Skip to content

Commit 2729360

Browse files
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.
1 parent 64b43df commit 2729360

File tree

5 files changed

+87
-54
lines changed

5 files changed

+87
-54
lines changed

lib/common/definitions/mobile.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ declare module Mobile {
149149
muted?: boolean;
150150
}
151151

152-
interface IDeviceAppData extends IPlatform {
152+
interface IDeviceAppData extends IPlatform, IConnectTimeoutOption {
153153
appIdentifier: string;
154154
device: Mobile.IDevice;
155155
getDeviceProjectRootPath(): Promise<string>;

lib/definitions/livesync.d.ts

+10-7
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,14 @@ interface IShouldSkipEmitLiveSyncNotification {
343343
interface IAttachDebuggerOptions extends IDebuggingAdditionalOptions, IEnableDebuggingDeviceOptions, IIsEmulator, IPlatform, IOptionalOutputPath {
344344
}
345345

346-
interface ILiveSyncWatchInfo extends IProjectDataComposition, IHasUseHotModuleReloadOption {
346+
interface IConnectTimeoutOption {
347+
/**
348+
* Time to wait for successful connection. Defaults to 30000 miliseconds.
349+
*/
350+
connectTimeout?: number;
351+
}
352+
353+
interface ILiveSyncWatchInfo extends IProjectDataComposition, IHasUseHotModuleReloadOption, IConnectTimeoutOption {
347354
filesToRemove: string[];
348355
filesToSync: string[];
349356
isReinstalled: boolean;
@@ -362,7 +369,7 @@ interface ILiveSyncResultInfo extends IHasUseHotModuleReloadOption {
362369

363370
interface IAndroidLiveSyncResultInfo extends ILiveSyncResultInfo, IAndroidLivesyncSyncOperationResult { }
364371

365-
interface IFullSyncInfo extends IProjectDataComposition, IHasUseHotModuleReloadOption {
372+
interface IFullSyncInfo extends IProjectDataComposition, IHasUseHotModuleReloadOption, IConnectTimeoutOption {
366373
device: Mobile.IDevice;
367374
watch: boolean;
368375
syncAllFiles: boolean;
@@ -510,7 +517,7 @@ interface IDoSyncOperationOptions {
510517
operationId?: string
511518
}
512519

513-
interface IAndroidLivesyncToolConfiguration {
520+
interface IAndroidLivesyncToolConfiguration extends IConnectTimeoutOption {
514521
/**
515522
* The application identifier.
516523
*/
@@ -531,10 +538,6 @@ interface IAndroidLivesyncToolConfiguration {
531538
* If provider will call it when an error occurs.
532539
*/
533540
errorHandler?: any;
534-
/**
535-
* Time to wait for successful connection. Defaults to 30000 miliseconds.
536-
*/
537-
connectTimeout?: number;
538541
}
539542

540543
interface IAndroidLivesyncSyncOperationResult {

lib/services/livesync/android-device-livesync-sockets-service.ts

+18-10
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,23 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe
2323
private $fs: IFileSystem,
2424
private $devicePlatformsConstants: Mobile.IDevicePlatformsConstants,
2525
$filesHashService: IFilesHashService) {
26-
super($injector, $platformsData, $filesHashService, $logger, device);
27-
this.livesyncTool = this.$injector.resolve(AndroidLivesyncTool);
26+
super($injector, $platformsData, $filesHashService, $logger, device);
27+
this.livesyncTool = this.$injector.resolve(AndroidLivesyncTool);
2828
}
2929

3030
public async beforeLiveSyncAction(deviceAppData: Mobile.IDeviceAppData): Promise<void> {
31-
const pathToLiveSyncFile = temp.path({ prefix: "livesync" });
32-
this.$fs.writeFile(pathToLiveSyncFile, "");
33-
await this.device.fileSystem.putFile(pathToLiveSyncFile, this.getPathToLiveSyncFileOnDevice(deviceAppData.appIdentifier), deviceAppData.appIdentifier);
34-
await this.device.applicationManager.startApplication({ appId: deviceAppData.appIdentifier, projectName: this.data.projectName, justLaunch: true });
35-
await this.connectLivesyncTool(this.data.projectIdentifiers.android);
31+
if (!this.livesyncTool.hasConnection()) {
32+
try {
33+
const pathToLiveSyncFile = temp.path({ prefix: "livesync" });
34+
this.$fs.writeFile(pathToLiveSyncFile, "");
35+
await this.device.fileSystem.putFile(pathToLiveSyncFile, this.getPathToLiveSyncFileOnDevice(deviceAppData.appIdentifier), deviceAppData.appIdentifier);
36+
await this.device.applicationManager.startApplication({ appId: deviceAppData.appIdentifier, projectName: this.data.projectName, justLaunch: true });
37+
await this.connectLivesyncTool(this.data.projectIdentifiers.android, deviceAppData.connectTimeout);
38+
} catch (err) {
39+
await this.device.fileSystem.deleteFile(this.getPathToLiveSyncFileOnDevice(deviceAppData.appIdentifier), deviceAppData.appIdentifier);
40+
throw err;
41+
}
42+
}
3643
}
3744

3845
private getPathToLiveSyncFileOnDevice(appIdentifier: string): string {
@@ -59,7 +66,7 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe
5966

6067
if (liveSyncInfo.modifiedFilesData.length) {
6168
const canExecuteFastSync = !liveSyncInfo.isFullSync && this.canExecuteFastSyncForPaths(liveSyncInfo, liveSyncInfo.modifiedFilesData, projectData, this.device.deviceInfo.platform);
62-
const doSyncPromise = this.livesyncTool.sendDoSyncOperation({ doRefresh: canExecuteFastSync, operationId});
69+
const doSyncPromise = this.livesyncTool.sendDoSyncOperation({ doRefresh: canExecuteFastSync, operationId });
6370

6471
const syncInterval: NodeJS.Timer = setInterval(() => {
6572
if (this.livesyncTool.isOperationInProgress(operationId)) {
@@ -114,14 +121,15 @@ export class AndroidDeviceSocketsLiveSyncService extends AndroidDeviceLiveSyncSe
114121
await this.livesyncTool.sendDirectory(projectFilesPath);
115122
}
116123

117-
private async connectLivesyncTool(appIdentifier: string) {
124+
private async connectLivesyncTool(appIdentifier: string, connectTimeout?: number) {
118125
const platformData = this.$platformsData.getPlatformData(this.$devicePlatformsConstants.Android, this.data);
119126
const projectFilesPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME);
120127
if (!this.livesyncTool.hasConnection()) {
121128
await this.livesyncTool.connect({
122129
appIdentifier,
123130
deviceIdentifier: this.device.deviceInfo.identifier,
124-
appPlatformsPath: projectFilesPath
131+
appPlatformsPath: projectFilesPath,
132+
connectTimeout
125133
});
126134
}
127135
}

lib/services/livesync/livesync-service.ts

+56-35
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
594594
let filesToRemove: string[] = [];
595595
let timeoutTimer: NodeJS.Timer;
596596

597-
const startSyncFilesTimeout = (platform?: string) => {
597+
const startSyncFilesTimeout = (platform?: string, opts?: { calledFromHook: boolean }) => {
598598
timeoutTimer = setTimeout(async () => {
599599
if (platform && liveSyncData.bundle) {
600600
filesToSync = filesToSyncMap[platform];
@@ -636,6 +636,52 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
636636
const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir];
637637
const deviceBuildInfoDescriptor = _.find(liveSyncProcessInfo.deviceDescriptors, dd => dd.identifier === device.deviceInfo.identifier);
638638

639+
const settings: ILiveSyncWatchInfo = {
640+
liveSyncDeviceInfo: deviceBuildInfoDescriptor,
641+
projectData,
642+
filesToRemove: currentFilesToRemove,
643+
filesToSync: currentFilesToSync,
644+
isReinstalled: false,
645+
syncAllFiles: liveSyncData.watchAllFiles,
646+
hmrData: currentHmrData,
647+
useHotModuleReload: liveSyncData.useHotModuleReload,
648+
force: liveSyncData.force,
649+
connectTimeout: 1000
650+
};
651+
652+
const service = this.getLiveSyncService(device.deviceInfo.platform);
653+
654+
const watchAction = async (watchInfo: ILiveSyncWatchInfo): Promise<void> => {
655+
let liveSyncResultInfo = await service.liveSyncWatchAction(device, watchInfo);
656+
657+
await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath);
658+
659+
// If didRecover is true, this means we were in ErrorActivity and fallback files were already transfered and app will be restarted.
660+
if (!liveSyncResultInfo.didRecover && liveSyncData.useHotModuleReload && currentHmrData.hash) {
661+
const status = await this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, currentHmrData.hash);
662+
if (status === HmrConstants.HMR_ERROR_STATUS) {
663+
watchInfo.filesToSync = currentHmrData.fallbackFiles[device.deviceInfo.platform];
664+
liveSyncResultInfo = await service.liveSyncWatchAction(device, watchInfo);
665+
// We want to force a restart of the application.
666+
liveSyncResultInfo.isFullSync = true;
667+
await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath);
668+
}
669+
}
670+
671+
this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`);
672+
};
673+
674+
if (liveSyncData.useHotModuleReload && opts && opts.calledFromHook) {
675+
try {
676+
this.$logger.trace("Try executing watch action without any preparation of files.");
677+
await watchAction(settings);
678+
this.$logger.trace("Successfully executed watch action without any preparation of files.");
679+
return;
680+
} catch (err) {
681+
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}`);
682+
}
683+
}
684+
639685
const appInstalledOnDeviceResult = await this.ensureLatestAppPackageIsInstalledOnDevice({
640686
device,
641687
preparedPlatforms,
@@ -653,41 +699,15 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
653699
skipModulesNativeCheck: !liveSyncData.watchAllFiles
654700
}, { skipNativePrepare: deviceBuildInfoDescriptor.skipNativePrepare });
655701

702+
settings.isReinstalled = appInstalledOnDeviceResult.appInstalled;
703+
settings.connectTimeout = null;
704+
656705
if (liveSyncData.useHotModuleReload && appInstalledOnDeviceResult.appInstalled) {
657706
const additionalFilesToSync = currentHmrData && currentHmrData.fallbackFiles && currentHmrData.fallbackFiles[device.deviceInfo.platform];
658707
_.each(additionalFilesToSync, fileToSync => currentFilesToSync.push(fileToSync));
659708
}
660709

661-
const service = this.getLiveSyncService(device.deviceInfo.platform);
662-
const settings: ILiveSyncWatchInfo = {
663-
liveSyncDeviceInfo: deviceBuildInfoDescriptor,
664-
projectData,
665-
filesToRemove: currentFilesToRemove,
666-
filesToSync: currentFilesToSync,
667-
isReinstalled: appInstalledOnDeviceResult.appInstalled,
668-
syncAllFiles: liveSyncData.watchAllFiles,
669-
hmrData: currentHmrData,
670-
useHotModuleReload: liveSyncData.useHotModuleReload,
671-
force: liveSyncData.force
672-
};
673-
674-
let liveSyncResultInfo = await service.liveSyncWatchAction(device, settings);
675-
676-
await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath);
677-
678-
//If didRecover is true, this means we were in ErrorActivity and fallback files were already transfered and app will be restarted.
679-
if (!liveSyncResultInfo.didRecover && liveSyncData.useHotModuleReload && currentHmrData.hash) {
680-
const status = await this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, currentHmrData.hash);
681-
if (status === HmrConstants.HMR_ERROR_STATUS) {
682-
settings.filesToSync = currentHmrData.fallbackFiles[device.deviceInfo.platform];
683-
liveSyncResultInfo = await service.liveSyncWatchAction(device, settings);
684-
//We want to force a restart of the application.
685-
liveSyncResultInfo.isFullSync = true;
686-
await this.refreshApplication(projectData, liveSyncResultInfo, deviceBuildInfoDescriptor.debugOptions, deviceBuildInfoDescriptor.outputPath);
687-
}
688-
}
689-
690-
this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`);
710+
await watchAction(settings);
691711
},
692712
(device: Mobile.IDevice) => {
693713
const liveSyncProcessInfo = this.liveSyncProcessesInfo[projectData.projectDir];
@@ -715,7 +735,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
715735
});
716736
}
717737
}
718-
}, 250);
738+
}, liveSyncData.useHotModuleReload ? 0 : 250);
719739

720740
this.liveSyncProcessesInfo[liveSyncData.projectDir].timer = timeoutTimer;
721741
};
@@ -738,11 +758,12 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
738758
hmrData,
739759
filesToRemove,
740760
startSyncFilesTimeout: async (platform: string) => {
761+
const opts = { calledFromHook: true };
741762
if (platform) {
742-
await startSyncFilesTimeout(platform);
763+
await startSyncFilesTimeout(platform, opts);
743764
} else {
744765
// This code is added for backwards compatibility with old versions of nativescript-dev-webpack plugin.
745-
await startSyncFilesTimeout();
766+
await startSyncFilesTimeout(null, opts);
746767
}
747768
}
748769
}
@@ -823,7 +844,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
823844
}
824845
}
825846

826-
public emitLivesyncEvent (event: string, livesyncData: ILiveSyncEventData): boolean {
847+
public emitLivesyncEvent(event: string, livesyncData: ILiveSyncEventData): boolean {
827848
this.$logger.trace(`Will emit event ${event} with data`, livesyncData);
828849
return this.emit(event, livesyncData);
829850
}

lib/services/livesync/platform-livesync-service-base.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,8 @@ export abstract class PlatformLiveSyncServiceBase {
135135
platform: syncInfo.device.deviceInfo.platform,
136136
getDeviceProjectRootPath: () => this.$devicePathProvider.getDeviceProjectRootPath(syncInfo.device, deviceProjectRootOptions),
137137
deviceSyncZipPath: this.$devicePathProvider.getDeviceSyncZipPath(syncInfo.device),
138-
isLiveSyncSupported: async () => true
138+
isLiveSyncSupported: async () => true,
139+
connectTimeout: syncInfo.connectTimeout
139140
};
140141
}
141142

0 commit comments

Comments
 (0)