Skip to content

Commit 042a043

Browse files
committed
feat: add support for fallbackFiles + hmr mode
1 parent 2b8d62c commit 042a043

File tree

7 files changed

+95
-25
lines changed

7 files changed

+95
-25
lines changed

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"program": "${workspaceRoot}/lib/nativescript-cli.js",
1616

1717
// example commands
18-
"args": [ "run", "ios", "--path", "${workspaceRoot}/scratch/webpackApp", "--bundle", "--env.sourceMap"]
18+
"args": [ "run", "ios", "--path", "${workspaceRoot}/scratch/webpackApp", "--hmr"]
1919
// "args": [ "test", "android", "--justlaunch"]
2020
// "args": [ "platform", "add", "android@1.3.0", "--path", "cliapp"]
2121
// "args": [ "platform", "remove", "android", "--path", "cliapp"]

lib/controllers/run-on-devices-controller.ts

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { LiveSyncServiceResolver } from "../resolvers/livesync-service-resolver"
77
import { RunOnDevicesDataService } from "../services/run-on-devices-data-service";
88
import { RunOnDevicesEmitter } from "../run-on-devices-emitter";
99
import { WorkflowDataService } from "../services/workflow/workflow-data-service";
10+
import { HmrConstants } from "../common/constants";
1011

1112
export class RunOnDevicesController extends EventEmitter {
1213
constructor(
@@ -15,6 +16,7 @@ export class RunOnDevicesController extends EventEmitter {
1516
private $deviceInstallAppService: DeviceInstallAppService,
1617
private $deviceRefreshAppService: DeviceRefreshAppService,
1718
private $devicesService: Mobile.IDevicesService,
19+
private $hmrStatusService: IHmrStatusService,
1820
public $hooksService: IHooksService,
1921
private $liveSyncServiceResolver: LiveSyncServiceResolver,
2022
private $logger: ILogger,
@@ -65,7 +67,7 @@ export class RunOnDevicesController extends EventEmitter {
6567
});
6668

6769
if (liveSyncResultInfo && deviceDescriptor.debugggingEnabled) {
68-
await this.$deviceDebugAppService.refreshApplicationWithDebug(projectData, deviceDescriptor, refreshInfo);
70+
await this.$deviceDebugAppService.enableDebugging(projectData, deviceDescriptor, refreshInfo);
6971
}
7072

7173
this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`);
@@ -89,28 +91,36 @@ export class RunOnDevicesController extends EventEmitter {
8991
await this.$buildPlatformService.buildPlatform(nativePlatformData, projectData, buildPlatformData);
9092
}
9193

94+
const isInHMRMode = liveSyncInfo.useHotModuleReload && data.hmrData && data.hmrData.hash;
95+
if (isInHMRMode) {
96+
this.$hmrStatusService.watchHmrStatus(device.deviceInfo.identifier, data.hmrData.hash);
97+
}
98+
9299
const platformLiveSyncService = this.$liveSyncServiceResolver.resolveLiveSyncService(device.deviceInfo.platform);
93-
const liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, {
100+
const watchInfo = {
94101
liveSyncDeviceInfo: deviceDescriptor,
95102
projectData,
96-
filesToRemove: [],
103+
filesToRemove: <any>[],
97104
filesToSync: data.files,
98105
isReinstalled: false,
99-
hmrData: null, // platformHmrData,
106+
hmrData: data.hmrData,
100107
useHotModuleReload: liveSyncInfo.useHotModuleReload,
101108
force: liveSyncInfo.force,
102109
connectTimeout: 1000
103-
});
104-
105-
const refreshInfo = await this.$deviceRefreshAppService.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, deviceDescriptor);
106-
107-
this.$runOnDevicesEmitter.emitRunOnDeviceExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, {
108-
syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()),
109-
isFullSync: liveSyncResultInfo.isFullSync
110-
});
111-
112-
if (liveSyncResultInfo && deviceDescriptor.debugggingEnabled) {
113-
await this.$deviceDebugAppService.refreshApplicationWithDebug(projectData, deviceDescriptor, refreshInfo);
110+
};
111+
let liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, watchInfo);
112+
113+
await this.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor);
114+
115+
if (!liveSyncResultInfo.didRecover && isInHMRMode) {
116+
const status = await this.$hmrStatusService.getHmrStatus(device.deviceInfo.identifier, data.hmrData.hash);
117+
if (status === HmrConstants.HMR_ERROR_STATUS) {
118+
watchInfo.filesToSync = data.hmrData.fallbackFiles;
119+
liveSyncResultInfo = await platformLiveSyncService.liveSyncWatchAction(device, watchInfo);
120+
// We want to force a restart of the application.
121+
liveSyncResultInfo.isFullSync = true;
122+
await this.refreshApplication(projectData, liveSyncResultInfo, deviceDescriptor);
123+
}
114124
}
115125

116126
this.$logger.info(`Successfully synced application ${liveSyncResultInfo.deviceAppData.appIdentifier} on device ${liveSyncResultInfo.deviceAppData.device.deviceInfo.identifier}.`);
@@ -126,6 +136,19 @@ export class RunOnDevicesController extends EventEmitter {
126136
}
127137
}
128138

139+
private async refreshApplication(projectData: IProjectData, liveSyncResultInfo: ILiveSyncResultInfo, deviceDescriptor: ILiveSyncDeviceInfo) {
140+
const refreshInfo = await this.$deviceRefreshAppService.refreshApplicationWithoutDebug(projectData, liveSyncResultInfo, deviceDescriptor);
141+
142+
this.$runOnDevicesEmitter.emitRunOnDeviceExecutedEvent(projectData, liveSyncResultInfo.deviceAppData.device, {
143+
syncedFiles: liveSyncResultInfo.modifiedFilesData.map(m => m.getLocalPath()),
144+
isFullSync: liveSyncResultInfo.isFullSync
145+
});
146+
147+
if (liveSyncResultInfo && deviceDescriptor.debugggingEnabled) {
148+
await this.$deviceDebugAppService.enableDebugging(projectData, deviceDescriptor, refreshInfo);
149+
}
150+
}
151+
129152
private async addActionToChain<T>(projectDir: string, action: () => Promise<T>): Promise<T> {
130153
const liveSyncInfo = this.$runOnDevicesDataService.getData(projectDir);
131154
if (liveSyncInfo) {

lib/services/device/device-debug-app-service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export class DeviceDebugAppService {
1414
) { }
1515

1616
@performanceLog()
17-
public async refreshApplicationWithDebug(projectData: IProjectData, deviceDescriptor: ILiveSyncDeviceInfo, refreshInfo: IRestartApplicationInfo): Promise<IDebugInformation> {
17+
public async enableDebugging(projectData: IProjectData, deviceDescriptor: ILiveSyncDeviceInfo, refreshInfo: IRestartApplicationInfo): Promise<IDebugInformation> {
1818
const { debugOptions } = deviceDescriptor;
1919
// we do not stop the application when debugBrk is false, so we need to attach, instead of launch
2020
// if we try to send the launch request, the debugger port will not be printed and the command will timeout

lib/services/platform/platform-watcher-service.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ export class PlatformWatcherService extends EventEmitter implements IPlatformWat
4444

4545
private async startJSWatcherWithPrepare(platformData: IPlatformData, projectData: IProjectData, config: IWebpackCompilerConfig): Promise<void> {
4646
if (!this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].webpackCompilerProcess) {
47-
this.$webpackCompilerService.on("webpackEmittedFiles", files => {
48-
this.emitFilesChangeEvent({ files, hasNativeChanges: false, platform: platformData.platformNameLowerCase });
47+
this.$webpackCompilerService.on("webpackEmittedFiles", data => {
48+
this.emitFilesChangeEvent({ ...data, hasNativeChanges: false, platform: platformData.platformNameLowerCase });
4949
});
5050

5151
const childProcess = await this.$webpackCompilerService.compileWithWatch(platformData, projectData, config);
@@ -75,7 +75,7 @@ export class PlatformWatcherService extends EventEmitter implements IPlatformWat
7575
.on("all", async (event: string, filePath: string) => {
7676
filePath = path.join(projectData.projectDir, filePath);
7777
this.$logger.trace(`Chokidar raised event ${event} for ${filePath}.`);
78-
this.emitFilesChangeEvent({ files: [], hasNativeChanges: true, platform: platformData.platformNameLowerCase });
78+
this.emitFilesChangeEvent({ files: [], hmrData: null, hasNativeChanges: true, platform: platformData.platformNameLowerCase });
7979
});
8080

8181
this.watchersData[projectData.projectDir][platformData.platformNameLowerCase].nativeFilesWatcher = watcher;

lib/services/webpack/webpack-compiler-service.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,18 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp
3535
const files = message.emittedFiles
3636
.filter((file: string) => file.indexOf("App_Resources") === -1)
3737
.map((file: string) => path.join(platformData.appDestinationDirectoryPath, "app", file));
38-
this.emit("webpackEmittedFiles", files);
38+
39+
const result = this.getUpdatedEmittedFiles(message.emittedFiles);
40+
41+
const data = {
42+
files,
43+
hmrData: {
44+
hash: result.hash,
45+
fallbackFiles: result.fallbackFiles
46+
}
47+
};
48+
49+
this.emit("webpackEmittedFiles", data);
3950
}
4051
});
4152

@@ -143,5 +154,34 @@ export class WebpackCompilerService extends EventEmitter implements IWebpackComp
143154

144155
return args;
145156
}
157+
158+
private getUpdatedEmittedFiles(emittedFiles: string[]) {
159+
let fallbackFiles: string[] = [];
160+
let hotHash;
161+
if (emittedFiles.some(x => x.endsWith('.hot-update.json'))) {
162+
let result = emittedFiles.slice();
163+
const hotUpdateScripts = emittedFiles.filter(x => x.endsWith('.hot-update.js'));
164+
hotUpdateScripts.forEach(hotUpdateScript => {
165+
const { name, hash } = this.parseHotUpdateChunkName(hotUpdateScript);
166+
hotHash = hash;
167+
// remove bundle/vendor.js files if there's a bundle.XXX.hot-update.js or vendor.XXX.hot-update.js
168+
result = result.filter(file => file !== `${name}.js`);
169+
});
170+
//if applying of hot update fails, we must fallback to the full files
171+
fallbackFiles = emittedFiles.filter(file => result.indexOf(file) === -1);
172+
return { emittedFiles: result, fallbackFiles, hash: hotHash };
173+
}
174+
175+
return { emittedFiles, fallbackFiles };
176+
}
177+
178+
private parseHotUpdateChunkName(name: string) {
179+
const matcher = /^(.+)\.(.+)\.hot-update/gm;
180+
const matches = matcher.exec(name);
181+
return {
182+
name: matches[1] || "",
183+
hash: matches[2] || "",
184+
};
185+
}
146186
}
147187
$injector.register("webpackCompilerService", WebpackCompilerService);

lib/services/webpack/webpack.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ declare global {
3232
interface IFilesChangeEventData {
3333
platform: string;
3434
files: string[];
35+
hmrData: IPlatformHmrData;
3536
hasNativeChanges: boolean;
3637
}
3738

lib/services/workflow/workflow-data-service.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,28 +39,34 @@ export class WorkflowDataService {
3939
}
4040

4141
private getAddPlatformData(platform: string, options: IOptions | any) {
42-
return {
42+
const result = {
4343
frameworkPath: options.frameworkPath,
4444
nativePrepare: options.nativePrepare,
4545
platformParam: options.platformParam || platform,
4646
};
47+
48+
return result;
4749
}
4850

4951
private getPreparePlatformData(options: IOptions | any) {
50-
return {
51-
env: options.env,
52+
const result = {
53+
env: { ...options.env, hmr: options.hmr || options.useHotModuleReload },
5254
release: options.release,
5355
nativePrepare: options.nativePrepare
5456
};
57+
58+
return result;
5559
}
5660

5761
private getIOSPrepareData(options: IOptions | any) {
58-
return {
62+
const result = {
5963
...this.getPreparePlatformData(options),
6064
teamId: options.teamId,
6165
provision: options.provision,
6266
mobileProvisionData: options.mobileProvisionData
6367
};
68+
69+
return result;
6470
}
6571
}
6672
$injector.register("workflowDataService", WorkflowDataService);

0 commit comments

Comments
 (0)