Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(livesync): enable livesync with bundling #3350

Merged
merged 3 commits into from
Feb 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions lib/commands/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,6 @@ export class RunCommandBase extends BundleBase implements ICommand {
}

public async executeCore(args: string[]): Promise<void> {
if (this.$options.bundle) {
this.$options.watch = false;
}

await this.$devicesService.initialize({
deviceId: this.$options.device,
platform: this.platform,
Expand Down
17 changes: 13 additions & 4 deletions lib/definitions/livesync.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,17 @@ interface ILiveSyncDeviceInfo extends IOptionalOutputPath, IOptionalDebuggingOpt
platformSpecificOptions?: IPlatformOptions;
}

/**
* Describes a LiveSync operation.
*/
interface ILiveSyncInfo extends IProjectDir, IEnvOptions, IBundle, IRelease {
interface IOptionalSkipWatcher {
/**
* Defines if the watcher should be skipped. If not passed, fs.Watcher will be started.
*/
skipWatcher?: boolean;
}

/**
* Describes a LiveSync operation.
*/
interface ILiveSyncInfo extends IProjectDir, IEnvOptions, IBundle, IRelease, IOptionalSkipWatcher {
/**
* Defines if all project files should be watched for changes. In case it is not passed, only `app` dir of the project will be watched for changes.
* In case it is set to true, the package.json of the project and node_modules directory will also be watched, so any change there will be transferred to device(s).
Expand Down Expand Up @@ -207,6 +209,13 @@ interface ILiveSyncService {
* Describes LiveSync operations while debuggging.
*/
interface IDebugLiveSyncService extends ILiveSyncService {
/**
* Method used to retrieve the glob patterns which CLI will watch for file changes. Defaults to the whole app directory.
* @param {ILiveSyncInfo} liveSyncData Information needed for livesync - for example if bundle is passed or if a release build should be performed.
* @returns {Promise<string[]>} The glob patterns.
*/
getWatcherPatterns(liveSyncData: ILiveSyncInfo): Promise<string[]>;

/**
* Prints debug information.
* @param {IDebugInformation} debugInformation Information to be printed.
Expand Down
43 changes: 30 additions & 13 deletions lib/services/livesync/livesync-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import * as choki from "chokidar";
import { EOL } from "os";
import { EventEmitter } from "events";
import { hook } from "../../common/helpers";
import { APP_FOLDER_NAME, PACKAGE_JSON_FILE_NAME, LiveSyncTrackActionNames, USER_INTERACTION_NEEDED_EVENT_NAME, DEBUGGER_ATTACHED_EVENT_NAME, DEBUGGER_DETACHED_EVENT_NAME, TrackActionNames } from "../../constants";
import { FileExtensions, DeviceTypes, DeviceDiscoveryEventNames } from "../../common/constants";
import { APP_FOLDER_NAME, APP_RESOURCES_FOLDER_NAME, PACKAGE_JSON_FILE_NAME, LiveSyncTrackActionNames, USER_INTERACTION_NEEDED_EVENT_NAME, DEBUGGER_ATTACHED_EVENT_NAME, DEBUGGER_DETACHED_EVENT_NAME, TrackActionNames } from "../../constants";
import { DeviceTypes, DeviceDiscoveryEventNames } from "../../common/constants";
import { cache } from "../../common/decorators";

const deviceDescriptorPrimaryKey = "identifier";
Expand Down Expand Up @@ -289,6 +289,12 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
return _.map(deviceOptions, d => this.disableDebuggingCore(d, debuggingAdditionalOptions));
}

@hook('watchPatterns')
public async getWatcherPatterns(liveSyncData: ILiveSyncInfo): Promise<string[]> {
// liveSyncData is used by plugins that make use of the watchPatterns hook
return [APP_FOLDER_NAME, path.join(APP_FOLDER_NAME, APP_RESOURCES_FOLDER_NAME)];
}

public async disableDebuggingCore(deviceOption: IDisableDebuggingDeviceOptions, debuggingAdditionalOptions: IDebuggingAdditionalOptions): Promise<void> {
const liveSyncProcessInfo = this.liveSyncProcessesInfo[debuggingAdditionalOptions.projectDir];
if (liveSyncProcessInfo.currentSyncAction) {
Expand Down Expand Up @@ -328,7 +334,10 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
// Should be set after prepare
this.$usbLiveSyncService.isInitialized = true;

await this.startWatcher(projectData, liveSyncData);
const devicesIds = deviceDescriptors.map(dd => dd.identifier);
const devices = _.filter(this.$devicesService.getDeviceInstances(), device => _.includes(devicesIds, device.deviceInfo.identifier));
const platforms = _(devices).map(device => device.deviceInfo.platform).uniq().value();
await this.startWatcher(projectData, liveSyncData, platforms);
}
}

Expand Down Expand Up @@ -515,8 +524,8 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
};
}

private async startWatcher(projectData: IProjectData, liveSyncData: ILiveSyncInfo): Promise<void> {
const patterns = [APP_FOLDER_NAME];
private async startWatcher(projectData: IProjectData, liveSyncData: ILiveSyncInfo, platforms: string[]): Promise<void> {
const patterns = await this.getWatcherPatterns(liveSyncData);

if (liveSyncData.watchAllFiles) {
const productionDependencies = this.$nodeModulesDependenciesBuilder.getProductionDependencies(projectData.projectDir);
Expand All @@ -535,18 +544,18 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
currentWatcherInfo.watcher.close();
}

let filesToSync: string[] = [];
const filesToSync: string[] = [];
let filesToRemove: string[] = [];
let timeoutTimer: NodeJS.Timer;

const startTimeout = () => {
const startSyncFilesTimeout = () => {
timeoutTimer = setTimeout(async () => {
// Push actions to the queue, do not start them simultaneously
await this.addActionToChain(projectData.projectDir, async () => {
if (filesToSync.length || filesToRemove.length) {
try {
const currentFilesToSync = _.cloneDeep(filesToSync);
filesToSync = [];
filesToSync.splice(0, filesToSync.length);

const currentFilesToRemove = _.cloneDeep(filesToRemove);
filesToRemove = [];
Expand Down Expand Up @@ -622,7 +631,18 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi

await this.$hooksService.executeBeforeHooks('watch', {
hookArgs: {
projectData
projectData,
config: {
env: liveSyncData.env,
appFilesUpdaterOptions: {
bundle: liveSyncData.bundle,
release: liveSyncData.release
},
platforms
},
filesToSync,
filesToRemove,
startSyncFilesTimeout: startSyncFilesTimeout.bind(this)
}
});

Expand Down Expand Up @@ -650,10 +670,7 @@ export class LiveSyncService extends EventEmitter implements IDebugLiveSyncServi
filesToRemove.push(filePath);
}

// Do not sync typescript files directly - wait for javascript changes to occur in order to restart the app only once
if (path.extname(filePath) !== FileExtensions.TYPESCRIPT_FILE) {
startTimeout();
}
startSyncFilesTimeout();
});

this.liveSyncProcessesInfo[liveSyncData.projectDir].watcherInfo = { watcher, patterns };
Expand Down
4 changes: 2 additions & 2 deletions lib/services/platform-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,11 @@ export class PlatformService extends EventEmitter implements IPlatformService {
const changesInfo = await this.initialPrepare(platformInfo.platform, platformData, platformInfo.appFilesUpdaterOptions, platformInfo.platformTemplate, platformInfo.projectData, platformInfo.config, platformInfo.nativePrepare, platformInfo);
const requiresNativePrepare = (!platformInfo.nativePrepare || !platformInfo.nativePrepare.skipNativePrepare) && changesInfo.nativePlatformStatus === constants.NativePlatformStatus.requiresPrepare;

if (changesInfo.hasChanges || platformInfo.appFilesUpdaterOptions.bundle || requiresNativePrepare) {
if (changesInfo.hasChanges || requiresNativePrepare) {
// Always clear up the app directory in platforms if `--bundle` value has changed in between builds or is passed in general
// this is done as user has full control over what goes in platforms when `--bundle` is passed
// and we may end up with duplicate symbols which would fail the build
if (changesInfo.bundleChanged || platformInfo.appFilesUpdaterOptions.bundle) {
if (changesInfo.bundleChanged) {
await this.cleanDestinationApp(platformInfo);
}

Expand Down