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

Don't prepare the project on preview command #4142

Merged
merged 4 commits into from
Nov 23, 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
1 change: 1 addition & 0 deletions lib/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ $injector.requirePublicClass("androidLivesyncTool", "./services/livesync/android
$injector.require("androidLiveSyncService", "./services/livesync/android-livesync-service");
$injector.require("iOSLiveSyncService", "./services/livesync/ios-livesync-service");
$injector.require("usbLiveSyncService", "./services/livesync/livesync-service"); // The name is used in https://github.com/NativeScript/nativescript-dev-typescript
$injector.require("previewAppFilesService", "./services/livesync/playground/preview-app-files-service");
$injector.require("previewAppLiveSyncService", "./services/livesync/playground/preview-app-livesync-service");
$injector.require("previewAppLogProvider", "./services/livesync/playground/preview-app-log-provider");
$injector.require("previewAppPluginsService", "./services/livesync/playground/preview-app-plugins-service");
Expand Down
4 changes: 4 additions & 0 deletions lib/common/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,10 @@ export function getWinRegPropertyValue(key: string, propertyName: string): Promi
});
}

export function stringify(value: any, replacer?: (key: string, value: any) => any, space?: string | number): string {
return JSON.stringify(value, replacer, space || 2);
}

//--- begin part copied from AngularJS

//The MIT License
Expand Down
10 changes: 10 additions & 0 deletions lib/definitions/preview-app-livesync.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,16 @@ declare global {
stopLiveSync(): Promise<void>;
}

interface IPreviewAppFilesService {
getInitialFilesPayload(liveSyncData: IPreviewAppLiveSyncData, platform: string, deviceId?: string): FilesPayload;
getFilesPayload(liveSyncData: IPreviewAppLiveSyncData, filesData: IPreviewAppFilesData, platform: string, deviceId?: string): FilesPayload;
}

interface IPreviewAppFilesData {
filesToSync: string[];
filesToRemove?: string[];
}

interface IPreviewAppLiveSyncData extends IProjectDir, IHasUseHotModuleReloadOption, IBundle, IEnvOptions { }

interface IPreviewSdkService extends EventEmitter {
Expand Down
89 changes: 89 additions & 0 deletions lib/services/livesync/playground/preview-app-files-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import * as path from "path";
import { APP_FOLDER_NAME, TNS_MODULES_FOLDER_NAME, APP_RESOURCES_FOLDER_NAME } from "../../../constants";
import { PreviewSdkEventNames } from "./preview-app-constants";
import { FilePayload, FilesPayload } from "nativescript-preview-sdk";
const isTextOrBinary = require('istextorbinary');

export class PreviewAppFilesService implements IPreviewAppFilesService {
private excludedFileExtensions = [".ts", ".sass", ".scss", ".less"];
private excludedFiles = [".DS_Store"];

constructor(
private $fs: IFileSystem,
private $logger: ILogger,
private $platformsData: IPlatformsData,
private $projectDataService: IProjectDataService,
private $projectFilesManager: IProjectFilesManager
) { }

public getInitialFilesPayload(data: IPreviewAppLiveSyncData, platform: string, deviceId?: string): FilesPayload {
const rootFilesDir = this.getRootFilesDir(data, platform);
const filesToSync = this.$projectFilesManager.getProjectFiles(rootFilesDir);
const payloads = this.getFilesPayload(data, { filesToSync }, platform, deviceId);
return payloads;
}

public getFilesPayload(data: IPreviewAppLiveSyncData, filesData: IPreviewAppFilesData, platform: string, deviceId?: string): FilesPayload {
const { filesToSync, filesToRemove } = filesData;

const filesToTransfer = filesToSync
.filter(file => file.indexOf(TNS_MODULES_FOLDER_NAME) === -1)
.filter(file => file.indexOf(APP_RESOURCES_FOLDER_NAME) === -1)
.filter(file => !_.includes(this.excludedFiles, path.basename(file)))
.filter(file => !_.includes(this.excludedFileExtensions, path.extname(file)));

this.$logger.trace(`Sending ${filesToTransfer.join("\n")}.`);

const rootFilesDir = this.getRootFilesDir(data, platform);
const payloadsToSync = _.map(filesToTransfer, file => this.createFilePayload(file, rootFilesDir, PreviewSdkEventNames.CHANGE_EVENT_NAME));
const payloadsToRemove = _.map(filesToRemove, file => this.createFilePayload(file, rootFilesDir, PreviewSdkEventNames.UNLINK_EVENT_NAME));
const payloads = payloadsToSync.concat(payloadsToRemove);

return {
files: payloads,
platform: platform,
hmrMode: data.useHotModuleReload ? 1 : 0,
deviceId
};
}

private createFilePayload(file: string, rootFilesDir: string, event: string): FilePayload {
let fileContents = "";
let binary = false;

if (event === PreviewSdkEventNames.CHANGE_EVENT_NAME) {
binary = isTextOrBinary.isBinarySync(file);
if (binary) {
const bitmap = <string>this.$fs.readFile(file);
const base64 = Buffer.from(bitmap).toString('base64');
fileContents = base64;
} else {
fileContents = this.$fs.readText(file);
}
}

const filePayload = {
event,
file: path.relative(rootFilesDir, file),
binary,
fileContents
};

return filePayload;
}

private getRootFilesDir(data: IPreviewAppLiveSyncData, platform: string): string {
const projectData = this.$projectDataService.getProjectData(data.projectDir);
const platformData = this.$platformsData.getPlatformData(platform, projectData);

let rootFilesDir = null;
if (data.bundle) {
rootFilesDir = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME);
} else {
rootFilesDir = projectData.getAppDirectoryPath();
}

return rootFilesDir;
}
}
$injector.register("previewAppFilesService", PreviewAppFilesService);
186 changes: 51 additions & 135 deletions lib/services/livesync/playground/preview-app-livesync-service.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,25 @@
import * as path from "path";
import { FilePayload, Device, FilesPayload } from "nativescript-preview-sdk";
import { PreviewSdkEventNames } from "./preview-app-constants";
import { APP_FOLDER_NAME, APP_RESOURCES_FOLDER_NAME, TNS_MODULES_FOLDER_NAME } from "../../../constants";
import { Device, FilesPayload } from "nativescript-preview-sdk";
import { APP_RESOURCES_FOLDER_NAME, APP_FOLDER_NAME } from "../../../constants";
import { HmrConstants } from "../../../common/constants";
const isTextOrBinary = require('istextorbinary');

interface ISyncFilesOptions {
filesToSync?: string[];
filesToRemove?: string[];
isInitialSync?: boolean;
skipPrepare?: boolean;
useHotModuleReload?: boolean;
deviceId?: string;
}
import { stringify } from "../../../common/helpers";

export class PreviewAppLiveSyncService implements IPreviewAppLiveSyncService {
private excludedFileExtensions = [".ts", ".sass", ".scss", ".less"];
private excludedFiles = [".DS_Store"];

private deviceInitializationPromise: IDictionary<Promise<FilesPayload>> = {};

constructor(private $fs: IFileSystem,
constructor(
private $errors: IErrors,
private $hooksService: IHooksService,
private $logger: ILogger,
private $platformService: IPlatformService,
private $platformsData: IPlatformsData,
private $projectDataService: IProjectDataService,
private $previewSdkService: IPreviewSdkService,
private $previewAppFilesService: IPreviewAppFilesService,
private $previewAppPluginsService: IPreviewAppPluginsService,
private $previewDevicesService: IPreviewDevicesService,
private $projectFilesManager: IProjectFilesManager,
private $hmrStatusService: IHmrStatusService,
private $projectFilesProvider: IProjectFilesProvider) { }
) { }

public async initialize(data: IPreviewAppLiveSyncData): Promise<void> {
await this.$previewSdkService.initialize(async (device: Device) => {
Expand All @@ -43,7 +31,7 @@ export class PreviewAppLiveSyncService implements IPreviewAppLiveSyncService {
return this.deviceInitializationPromise[device.id];
}

this.deviceInitializationPromise[device.id] = this.initializePreviewForDevice(data, device);
this.deviceInitializationPromise[device.id] = this.getInitialFilesForDevice(data, device);
try {
const payloads = await this.deviceInitializationPromise[device.id];
return payloads;
Expand All @@ -65,27 +53,28 @@ export class PreviewAppLiveSyncService implements IPreviewAppLiveSyncService {
.map(device => device.platform)
.uniq()
.value();

for (const platform of platforms) {
await this.syncFilesForPlatformSafe(data, platform, { filesToSync, filesToRemove, useHotModuleReload: data.useHotModuleReload });
await this.syncFilesForPlatformSafe(data, { filesToSync, filesToRemove }, platform);
}
}

public async stopLiveSync(): Promise<void> {
this.$previewSdkService.stop();
}

private async initializePreviewForDevice(data: IPreviewAppLiveSyncData, device: Device): Promise<FilesPayload> {
private async getInitialFilesForDevice(data: IPreviewAppLiveSyncData, device: Device): Promise<FilesPayload> {
const hookArgs = this.getHookArgs(data, device);
await this.$hooksService.executeBeforeHooks("preview-sync", { hookArgs });
await this.$previewAppPluginsService.comparePluginsOnDevice(data, device);
const payloads = await this.syncFilesForPlatformSafe(data, device.platform, { isInitialSync: true, useHotModuleReload: data.useHotModuleReload });
const payloads = await this.getInitialFilesForPlatformSafe(data, device.platform);
return payloads;
}

private getHookArgs(data: IPreviewAppLiveSyncData, device: Device) {
const filesToSyncMap: IDictionary<string[]> = {};
const hmrData: IDictionary<IPlatformHmrData> = {};
const promise = Promise.resolve<FilesPayload>(null);
const promise = Promise.resolve();
const result = {
projectData: this.$projectDataService.getProjectData(data.projectDir),
hmrData,
Expand All @@ -106,14 +95,44 @@ export class PreviewAppLiveSyncService implements IPreviewAppLiveSyncService {
return result;
}

private async onWebpackCompilationComplete(data: IPreviewAppLiveSyncData, hmrData: IDictionary<IPlatformHmrData>, filesToSyncMap: IDictionary<string[]>, promise: Promise<FilesPayload>, platform: string) {
private async getInitialFilesForPlatformSafe(data: IPreviewAppLiveSyncData, platform: string): Promise<FilesPayload> {
this.$logger.info(`Start sending initial files for platform ${platform}.`);

try {
const payloads = this.$previewAppFilesService.getInitialFilesPayload(data, platform);
this.$logger.info(`Successfully sent initial files for platform ${platform}.`);
return payloads;
} catch (err) {
this.$logger.warn(`Unable to apply changes for platform ${platform}. Error is: ${err}, ${stringify(err)}`);
}
}

private async syncFilesForPlatformSafe(data: IPreviewAppLiveSyncData, filesData: IPreviewAppFilesData, platform: string, deviceId?: string): Promise<void> {
this.$logger.info(`Start syncing changes for platform ${platform}.`);

try {
const payloads = this.$previewAppFilesService.getFilesPayload(data, filesData, platform);
await this.$previewSdkService.applyChanges(payloads);
this.$logger.info(`Successfully synced ${payloads.files.map(filePayload => filePayload.file.yellow)} for platform ${platform}.`);
} catch (err) {
this.$logger.warn(`Unable to apply changes for platform ${platform}. Error is: ${err}, ${stringify(err)}.`);
}
}

private async onWebpackCompilationComplete(data: IPreviewAppLiveSyncData, hmrData: IDictionary<IPlatformHmrData>, filesToSyncMap: IDictionary<string[]>, promise: Promise<void>, platform: string) {
await promise
.then(async () => {
const currentHmrData = _.cloneDeep(hmrData);
const platformHmrData = currentHmrData[platform] || <any>{};
const filesToSync = _.cloneDeep(filesToSyncMap[platform]);
// We don't need to prepare when webpack emits changed files. We just need to send a message to pubnub.
promise = this.syncFilesForPlatformSafe(data, platform, { filesToSync, skipPrepare: true, useHotModuleReload: data.useHotModuleReload });
const projectData = this.$projectDataService.getProjectData(data.projectDir);
const platformData = this.$platformsData.getPlatformData(platform, projectData);
const clonedFiles = _.cloneDeep(filesToSyncMap[platform]);
const filesToSync = _.map(clonedFiles, fileToSync => {
const result = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME, path.relative(projectData.getAppDirectoryPath(), fileToSync));
return result;
});

promise = this.syncFilesForPlatformSafe(data, { filesToSync }, platform);
await promise;

if (data.useHotModuleReload && platformHmrData.hash) {
Expand All @@ -122,123 +141,20 @@ export class PreviewAppLiveSyncService implements IPreviewAppLiveSyncService {
await Promise.all(_.map(devices, async (previewDevice: Device) => {
const status = await this.$hmrStatusService.getHmrStatus(previewDevice.id, platformHmrData.hash);
if (status === HmrConstants.HMR_ERROR_STATUS) {
await this.syncFilesForPlatformSafe(data, platform, { filesToSync: platformHmrData.fallbackFiles, useHotModuleReload: false, deviceId: previewDevice.id });
const originalUseHotModuleReload = data.useHotModuleReload;
data.useHotModuleReload = false;
await this.syncFilesForPlatformSafe(data, { filesToSync: platformHmrData.fallbackFiles }, platform, previewDevice.id );
data.useHotModuleReload = originalUseHotModuleReload;
}
}));
}
});
filesToSyncMap[platform] = [];
}

private async syncFilesForPlatformSafe(data: IPreviewAppLiveSyncData, platform: string, opts?: ISyncFilesOptions): Promise<FilesPayload> {
this.$logger.info(`Start syncing changes for platform ${platform}.`);

opts = opts || {};
let payloads = null;

try {
const { env, projectDir } = data;
const projectData = this.$projectDataService.getProjectData(projectDir);
const platformData = this.$platformsData.getPlatformData(platform, projectData);

if (!opts.skipPrepare) {
await this.preparePlatform(platform, data, env, projectData);
}

if (opts.isInitialSync) {
const platformsAppFolderPath = path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME);
opts.filesToSync = this.$projectFilesManager.getProjectFiles(platformsAppFolderPath);
payloads = this.getFilesPayload(platformData, projectData, opts);
this.$logger.info(`Successfully synced changes for platform ${platform}.`);
} else {
opts.filesToSync = _.map(opts.filesToSync, file => this.$projectFilesProvider.mapFilePath(file, platformData.normalizedPlatformName, projectData));
payloads = this.getFilesPayload(platformData, projectData, opts);
await this.$previewSdkService.applyChanges(payloads);
this.$logger.info(`Successfully synced ${payloads.files.map(filePayload => filePayload.file.yellow)} for platform ${platform}.`);
}

return payloads;
} catch (err) {
this.$logger.warn(`Unable to apply changes for platform ${platform}. Error is: ${err}, ${JSON.stringify(err, null, 2)}.`);
}
}

private getFilesPayload(platformData: IPlatformData, projectData: IProjectData, opts?: ISyncFilesOptions): FilesPayload {
const { filesToSync, filesToRemove, deviceId } = opts;

const filesToTransfer = filesToSync
.filter(file => file.indexOf(TNS_MODULES_FOLDER_NAME) === -1)
.filter(file => file.indexOf(APP_RESOURCES_FOLDER_NAME) === -1)
.filter(file => !_.includes(this.excludedFiles, path.basename(file)))
.filter(file => !_.includes(this.excludedFileExtensions, path.extname(file)));

this.$logger.trace(`Transferring ${filesToTransfer.join("\n")}.`);

const payloadsToSync = filesToTransfer.map(file => this.createFilePayload(file, platformData, projectData, PreviewSdkEventNames.CHANGE_EVENT_NAME));
const payloadsToRemove = _.map(filesToRemove, file => this.createFilePayload(file, platformData, projectData, PreviewSdkEventNames.UNLINK_EVENT_NAME));
const payloads = payloadsToSync.concat(payloadsToRemove);

const hmrMode = opts.useHotModuleReload ? 1 : 0;
return { files: payloads, platform: platformData.normalizedPlatformName.toLowerCase(), hmrMode, deviceId };
}

private async preparePlatform(platform: string, data: IPreviewAppLiveSyncData, env: Object, projectData: IProjectData): Promise<void> {
const appFilesUpdaterOptions = {
bundle: data.bundle,
useHotModuleReload: data.useHotModuleReload,
release: false
};
const nativePrepare = { skipNativePrepare: true };
const config = <IPlatformOptions>{};
const platformTemplate = <string>null;
const prepareInfo = {
platform,
appFilesUpdaterOptions,
env,
projectData,
nativePrepare,
config,
platformTemplate,
skipCopyTnsModules: true,
skipCopyAppResourcesFiles: true
};
await this.$platformService.preparePlatform(prepareInfo);
}

private showWarningsForNativeFiles(files: string[]): void {
_.filter(files, file => file.indexOf(APP_RESOURCES_FOLDER_NAME) > -1)
.forEach(file => this.$logger.warn(`Unable to apply changes from ${APP_RESOURCES_FOLDER_NAME} folder. You need to build your application in order to make changes in ${APP_RESOURCES_FOLDER_NAME} folder.`));
}

private createFilePayload(file: string, platformData: IPlatformData, projectData: IProjectData, event: string): FilePayload {
const projectFileInfo = this.$projectFilesProvider.getProjectFileInfo(file, platformData.normalizedPlatformName, null);
const binary = isTextOrBinary.isBinarySync(file);
let fileContents = "";
let filePath = "";

if (event === PreviewSdkEventNames.CHANGE_EVENT_NAME) {
const relativePath = path.relative(path.join(platformData.appDestinationDirectoryPath, APP_FOLDER_NAME), file);
filePath = path.join(path.dirname(relativePath), projectFileInfo.onDeviceFileName);

if (binary) {
const bitmap = <string>this.$fs.readFile(file);
const base64 = Buffer.from(bitmap).toString('base64');
fileContents = base64;
} else {
fileContents = this.$fs.readText(path.join(path.dirname(projectFileInfo.filePath), projectFileInfo.onDeviceFileName));
}
} else if (event === PreviewSdkEventNames.UNLINK_EVENT_NAME) {
filePath = path.relative(path.join(projectData.projectDir, APP_FOLDER_NAME), file);
}

const filePayload = {
event,
file: filePath,
binary,
fileContents
};

return filePayload;
}
}
$injector.register("previewAppLiveSyncService", PreviewAppLiveSyncService);
Loading