From 27293609c02b9644dcbffb36c3e638a9c20b9f00 Mon Sep 17 00:00:00 2001 From: rosen-vladimirov Date: Fri, 12 Oct 2018 11:30:25 +0300 Subject: [PATCH] feat: speed-up livesync with HMR In case HMR is used, webpack reports to CLI the already prepared files, so there's no need for CLI to do any preparation, just sync the files. Try sending them as soon as possible and if we fail, go through the normal worklfow. Ensure the code is called only when webpack had produced files for us and we are with HMR enabled. --- lib/common/definitions/mobile.d.ts | 2 +- lib/definitions/livesync.d.ts | 17 ++-- ...android-device-livesync-sockets-service.ts | 28 ++++-- lib/services/livesync/livesync-service.ts | 91 ++++++++++++------- .../platform-livesync-service-base.ts | 3 +- 5 files changed, 87 insertions(+), 54 deletions(-) diff --git a/lib/common/definitions/mobile.d.ts b/lib/common/definitions/mobile.d.ts index 82c43369ca..3fa23012fa 100644 --- a/lib/common/definitions/mobile.d.ts +++ b/lib/common/definitions/mobile.d.ts @@ -149,7 +149,7 @@ declare module Mobile { muted?: boolean; } - interface IDeviceAppData extends IPlatform { + interface IDeviceAppData extends IPlatform, IConnectTimeoutOption { appIdentifier: string; device: Mobile.IDevice; getDeviceProjectRootPath(): Promise; diff --git a/lib/definitions/livesync.d.ts b/lib/definitions/livesync.d.ts index 6bf3a8a72e..d6619dadda 100644 --- a/lib/definitions/livesync.d.ts +++ b/lib/definitions/livesync.d.ts @@ -343,7 +343,14 @@ interface IShouldSkipEmitLiveSyncNotification { interface IAttachDebuggerOptions extends IDebuggingAdditionalOptions, IEnableDebuggingDeviceOptions, IIsEmulator, IPlatform, IOptionalOutputPath { } -interface ILiveSyncWatchInfo extends IProjectDataComposition, IHasUseHotModuleReloadOption { +interface IConnectTimeoutOption { + /** + * Time to wait for successful connection. Defaults to 30000 miliseconds. + */ + connectTimeout?: number; +} + +interface ILiveSyncWatchInfo extends IProjectDataComposition, IHasUseHotModuleReloadOption, IConnectTimeoutOption { filesToRemove: string[]; filesToSync: string[]; isReinstalled: boolean; @@ -362,7 +369,7 @@ interface ILiveSyncResultInfo extends IHasUseHotModuleReloadOption { interface IAndroidLiveSyncResultInfo extends ILiveSyncResultInfo, IAndroidLivesyncSyncOperationResult { } -interface IFullSyncInfo extends IProjectDataComposition, IHasUseHotModuleReloadOption { +interface IFullSyncInfo extends IProjectDataComposition, IHasUseHotModuleReloadOption, IConnectTimeoutOption { device: Mobile.IDevice; watch: boolean; syncAllFiles: boolean; @@ -510,7 +517,7 @@ interface IDoSyncOperationOptions { operationId?: string } -interface IAndroidLivesyncToolConfiguration { +interface IAndroidLivesyncToolConfiguration extends IConnectTimeoutOption { /** * The application identifier. */ @@ -531,10 +538,6 @@ interface IAndroidLivesyncToolConfiguration { * If provider will call it when an error occurs. */ errorHandler?: any; - /** - * Time to wait for successful connection. Defaults to 30000 miliseconds. - */ - connectTimeout?: number; } interface IAndroidLivesyncSyncOperationResult { diff --git a/lib/services/livesync/android-device-livesync-sockets-service.ts b/lib/services/livesync/android-device-livesync-sockets-service.ts index f68e911bbc..43cd7f7fd1 100644 --- a/lib/services/livesync/android-device-livesync-sockets-service.ts +++ b/lib/services/livesync/android-device-livesync-sockets-service.ts @@ -23,16 +23,23 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe private $fs: IFileSystem, private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants, $filesHashService: IFilesHashService) { - super($injector, $platformsData, $filesHashService, $logger, device); - this.livesyncTool = this.$injector.resolve(AndroidLivesyncTool); + super($injector, $platformsData, $filesHashService, $logger, device); + this.livesyncTool = this.$injector.resolve(AndroidLivesyncTool); } public async beforeLiveSyncAction(deviceAppData: Mobile.IDeviceAppData): Promise { - const pathToLiveSyncFile = temp.path({ prefix: "livesync" }); - this.$fs.writeFile(pathToLiveSyncFile, ""); - await this.device.fileSystem.putFile(pathToLiveSyncFile, this.getPathToLiveSyncFileOnDevice(deviceAppData.appIdentifier), deviceAppData.appIdentifier); - await this.device.applicationManager.startApplication({ appId: deviceAppData.appIdentifier, projectName: this.data.projectName, justLaunch: true }); - await this.connectLivesyncTool(this.data.projectIdentifiers.android); + if (!this.livesyncTool.hasConnection()) { + try { + const pathToLiveSyncFile = temp.path({ prefix: "livesync" }); + this.$fs.writeFile(pathToLiveSyncFile, ""); + await this.device.fileSystem.putFile(pathToLiveSyncFile, this.getPathToLiveSyncFileOnDevice(deviceAppData.appIdentifier), deviceAppData.appIdentifier); + await this.device.applicationManager.startApplication({ appId: deviceAppData.appIdentifier, projectName: this.data.projectName, justLaunch: true }); + await this.connectLivesyncTool(this.data.projectIdentifiers.android, deviceAppData.connectTimeout); + } catch (err) { + await this.device.fileSystem.deleteFile(this.getPathToLiveSyncFileOnDevice(deviceAppData.appIdentifier), deviceAppData.appIdentifier); + throw err; + } + } } private getPathToLiveSyncFileOnDevice(appIdentifier: string): string { @@ -59,7 +66,7 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe if (liveSyncInfo.modifiedFilesData.length) { const canExecuteFastSync = !liveSyncInfo.isFullSync && this.canExecuteFastSyncForPaths(liveSyncInfo, liveSyncInfo.modifiedFilesData, projectData, this.device.deviceInfo.platform); - const doSyncPromise = this.livesyncTool.sendDoSyncOperation({ doRefresh: canExecuteFastSync, operationId}); + const doSyncPromise = this.livesyncTool.sendDoSyncOperation({ doRefresh: canExecuteFastSync, operationId }); const syncInterval: NodeJS.Timer = setInterval(() => { if (this.livesyncTool.isOperationInProgress(operationId)) { @@ -114,14 +121,15 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe await this.livesyncTool.sendDirectory(projectFilesPath); } - private async connectLivesyncTool(appIdentifier: string) { + private async connectLivesyncTool(appIdentifier: string, connectTimeout?: number) { const platformData = this.$platformsData.getPlatformData(this.$devicePlatformsConstants.Android, this.data); const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME); if (!this.livesyncTool.hasConnection()) { await this.livesyncTool.connect({ appIdentifier, deviceIdentifier: this.device.deviceInfo.identifier, - appPlatformsPath: projectFilesPath + appPlatformsPath: projectFilesPath, + connectTimeout }); } } diff --git a/lib/services/livesync/livesync-service.ts b/lib/services/livesync/livesync-service.ts index 063de63e42..4ad472d760 100644 --- a/lib/services/livesync/livesync-service.ts +++ b/lib/services/livesync/livesync-service.ts @@ -594,7 +594,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi let filesToRemove: string[] = []; let timeoutTimer: NodeJS.Timer; - const startSyncFilesTimeout = (platform?: string) => { + const startSyncFilesTimeout = (platform?: string, opts?: { calledFromHook: boolean }) => { timeoutTimer = setTimeout(async () => { if (platform && liveSyncData.bundle) { filesToSync = filesToSyncMap[platform]; @@ -636,6 +636,52 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; const deviceBuildInfoDescriptor = _.find(liveSyncProcessInfo.deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier); + const settings: ILiveSyncWatchInfo = { + liveSyncDeviceInfo: deviceBuildInfoDescriptor, + projectData, + filesToRemove: currentFilesToRemove, + filesToSync: currentFilesToSync, + isReinstalled: false, + syncAllFiles: liveSyncData.watchAllFiles, + hmrData: currentHmrData, + useHotModuleReload: liveSyncData.useHotModuleReload, + force: liveSyncData.force, + connectTimeout: 1000 + }; + + const service = this.getLiveSyncService(device.deviceInfo.platform); + + const watchAction = async (watchInfo: ILiveSyncWatchInfo): Promise => { + let liveSyncResultInfo = await service.liveSyncWatchAction(device, watchInfo); + + await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); + + // If didRecover is true, this means we were in ErrorActivity and fallback files were already transfered and app will be restarted. + if (!liveSyncResultInfo.didRecover && liveSyncData.useHotModuleReload && currentHmrData.hash) { + const status = await this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, currentHmrData.hash); + if (status === HmrConstants.HMR_ERROR_STATUS) { + watchInfo.filesToSync = currentHmrData.fallbackFiles[device.deviceInfo.platform]; + liveSyncResultInfo = await service.liveSyncWatchAction(device, watchInfo); + // We want to force a restart of the application. + liveSyncResultInfo.isFullSync = true; + await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); + } + } + + this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); + }; + + if (liveSyncData.useHotModuleReload && opts && opts.calledFromHook) { + try { + this.$logger.trace("Try executing watch action without any preparation of files."); + await watchAction(settings); + this.$logger.trace("Successfully executed watch action without any preparation of files."); + return; + } catch (err) { + this.$logger.trace(`Error while trying to execute fast sync. Now we'll check the state of the app and we'll try to resurrect from the error. The error is: ${err}`); + } + } + const appInstalledOnDeviceResult = await this.ensureLatestAppPackageIsInstalledOnDevice({ device, preparedPlatforms, @@ -653,41 +699,15 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi skipModulesNativeCheck: !liveSyncData.watchAllFiles }, { skipNativePrepare: deviceBuildInfoDescriptor.skipNativePrepare }); + settings.isReinstalled = appInstalledOnDeviceResult.appInstalled; + settings.connectTimeout = null; + if (liveSyncData.useHotModuleReload && appInstalledOnDeviceResult.appInstalled) { const additionalFilesToSync = currentHmrData && currentHmrData.fallbackFiles && currentHmrData.fallbackFiles[device.deviceInfo.platform]; _.each(additionalFilesToSync, fileToSync => currentFilesToSync.push(fileToSync)); } - const service = this.getLiveSyncService(device.deviceInfo.platform); - const settings: ILiveSyncWatchInfo = { - liveSyncDeviceInfo: deviceBuildInfoDescriptor, - projectData, - filesToRemove: currentFilesToRemove, - filesToSync: currentFilesToSync, - isReinstalled: appInstalledOnDeviceResult.appInstalled, - syncAllFiles: liveSyncData.watchAllFiles, - hmrData: currentHmrData, - useHotModuleReload: liveSyncData.useHotModuleReload, - force: liveSyncData.force - }; - - let liveSyncResultInfo = await service.liveSyncWatchAction(device, settings); - - await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); - - //If didRecover is true, this means we were in ErrorActivity and fallback files were already transfered and app will be restarted. - if (!liveSyncResultInfo.didRecover && liveSyncData.useHotModuleReload && currentHmrData.hash) { - const status = await this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, currentHmrData.hash); - if (status === HmrConstants.HMR_ERROR_STATUS) { - settings.filesToSync = currentHmrData.fallbackFiles[device.deviceInfo.platform]; - liveSyncResultInfo = await service.liveSyncWatchAction(device, settings); - //We want to force a restart of the application. - liveSyncResultInfo.isFullSync = true; - await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath); - } - } - - this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`); + await watchAction(settings); }, (device: Mobile.IDevice) => { const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir]; @@ -715,7 +735,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi }); } } - }, 250); + }, liveSyncData.useHotModuleReload ? 0 : 250); this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer; }; @@ -738,11 +758,12 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi hmrData, filesToRemove, startSyncFilesTimeout: async (platform: string) => { + const opts = { calledFromHook: true }; if (platform) { - await startSyncFilesTimeout(platform); + await startSyncFilesTimeout(platform, opts); } else { // This code is added for backwards compatibility with old versions of nativescript-dev-webpack plugin. - await startSyncFilesTimeout(); + await startSyncFilesTimeout(null, opts); } } } @@ -823,7 +844,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi } } - public emitLivesyncEvent (event: string, livesyncData: ILiveSyncEventData): boolean { + public emitLivesyncEvent(event: string, livesyncData: ILiveSyncEventData): boolean { this.$logger.trace(`Will emit event ${event} with data`, livesyncData); return this.emit(event, livesyncData); } diff --git a/lib/services/livesync/platform-livesync-service-base.ts b/lib/services/livesync/platform-livesync-service-base.ts index 95c5c3d08a..fc08a776a5 100644 --- a/lib/services/livesync/platform-livesync-service-base.ts +++ b/lib/services/livesync/platform-livesync-service-base.ts @@ -135,7 +135,8 @@ export abstract class PlatformLiveSyncServiceBase { platform: syncInfo.device.deviceInfo.platform, getDeviceProjectRootPath: () => this.$devicePathProvider.getDeviceProjectRootPath(syncInfo.device, deviceProjectRootOptions), deviceSyncZipPath: this.$devicePathProvider.getDeviceSyncZipPath(syncInfo.device), - isLiveSyncSupported: async () => true + isLiveSyncSupported: async () => true, + connectTimeout: syncInfo.connectTimeout }; }