Skip to content

Commit eecb511

Browse files
committed
feat(windows): vite HMR livesync, comment cleanup
Windows livesync now hot-refreshes a running app when the bundler is vite with HMR on, and still launches it on the first sync. Adds windows to the bundler's user project platforms and drops a stale app.nsbundle on dev builds.
1 parent 6286e82 commit eecb511

11 files changed

Lines changed: 54 additions & 29 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ With the NativeScript CLI, you can target the following platforms.
9999

100100
* Android 4.2 or a later stable official release
101101
* iOS 9.0 or later stable official release
102-
* Windows 10 version 1809 (build 17763) or later — via `@nativescript/windows`
102+
* Windows 10 version 1809 (build 17763) or later. Via `@nativescript/windows`
103103

104104
[Back to Top][1]
105105

@@ -314,7 +314,7 @@ After you have listed the available devices, you can quickly run your app by exe
314314
```Shell
315315
ns run android
316316
ns run ios
317-
ns run windows # Windows only — runs on the local machine
317+
ns run windows # Windows only. Runs on the local machine
318318
```
319319

320320
[Back to Top][1]

‎docs/man_pages/project/testing/debug-windows.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ position: 7
77

88
### Description
99

10-
Initiates a debugging session for your project on the local Windows machine. When necessary, the command will prepare, build, deploy and launch the app before starting the debug session. The NativeScript runtime starts a Chrome DevTools Protocol server on port 9229 — attach Chrome DevTools or any CDP-compatible debugger to `ws://localhost:9229`.
10+
Initiates a debugging session for your project on the local Windows machine. When necessary, the command will prepare, build, deploy and launch the app before starting the debug session. The NativeScript runtime starts a Chrome DevTools Protocol server on port 9229. Attach Chrome DevTools or any CDP-compatible debugger to `ws://localhost:9229`.
1111

1212
<% if(isConsole && (isLinux || isMacOS)) { %>WARNING: You can run this command only on Windows systems. To view the complete help for this command, run `$ ns help debug windows`<% } %>
1313
<% if((isConsole && isWindows) || isHtml) { %>
@@ -45,7 +45,7 @@ Attach the debug tools to a running app | `$ ns debug windows --start [--timeout
4545
1. Run `ns debug windows`
4646
2. Open Chrome and navigate to `chrome://inspect`
4747
3. Under **Devices**, click **Configure** and add `localhost:9229`
48-
4. The NativeScript runtime will appear under **Remote Target** — click **inspect**
48+
4. The NativeScript runtime will appear under **Remote Target**. Click **inspect**
4949

5050
### Command Limitations
5151

‎lib/commands/build.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ export class BuildWindowsCommand extends BuildCommandBase implements ICommand {
331331
let canExecute = await super.canExecuteCommandBase(platform);
332332
if (canExecute) {
333333
// A sideload release package without signing material can be produced,
334-
// but it won't be installable until signed — warn rather than fail.
334+
// but it won't be installable until signed. Warn rather than fail.
335335
if (
336336
this.$options.release &&
337337
!this.$options.storeUpload &&

‎lib/common/mobile/windows/windows-application-manager.ts‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,23 +98,23 @@ export class WindowsApplicationManager extends ApplicationManagerBase {
9898
}
9999
// Two distinct artifact shapes reach here (see WindowsProjectService's
100100
// getValidBuildOutputData): a debug build resolves to the loose bin/ output's
101-
// AppxManifest.xml, meant for `-Register` (in-place dev registration — no signature
101+
// AppxManifest.xml, meant for `-Register` (in-place dev registration; no signature
102102
// validation, matches "AppxManifest.xml triggers Add-AppxPackage -Register (dev flow)");
103103
// a release build resolves to the packaged .msix/.msixupload under AppPackages/, which
104104
// `-Register` rejects outright ("An invalid manifest file name was passed to this
105-
// function") and which instead needs a plain package install via `-Path` — exactly what
105+
// function") and which instead needs a plain package install via `-Path`. Exactly what
106106
// the generated Install.ps1/Add-AppDevPackage.ps1 next to it do. A `-Path` install of an
107107
// unsigned release package still fails signature validation (0x800B0100); that's expected
108-
// — pass `--certificate`/`--certificate-thumbprint` at build time for a sideloadable
108+
// pass `--certificate`/`--certificate-thumbprint` at build time for a sideloadable
109109
// release package, this isn't something the install step can paper over.
110110
const isLooseManifest = packageFilePath.toLowerCase().endsWith("appxmanifest.xml");
111111
let addAppxCommand: string;
112112
if (isLooseManifest) {
113113
addAppxCommand = `Add-AppxPackage -ForceApplicationShutdown -Register -Path "${packageFilePath}"`;
114114
} else {
115115
// Thread through any Dependencies\<arch>\*.msix (e.g. the Windows App SDK runtime
116-
// framework package) the same way Add-AppDevPackage.ps1 does via `-DependencyPath` —
117-
// needed on a machine that doesn't already have that framework package installed.
116+
// framework package) the same way Add-AppDevPackage.ps1 does via `-DependencyPath`.
117+
// Needed on a machine that doesn't already have that framework package installed.
118118
const arch = process.arch === "arm64" ? "arm64" : "x64";
119119
const dependencyDir = path.join(path.dirname(packageFilePath), "Dependencies", arch);
120120
const dependencyGlob = path.join(dependencyDir, "*.msix");
@@ -176,7 +176,7 @@ export class WindowsApplicationManager extends ApplicationManagerBase {
176176
* The runtime writes console.log to ApplicationData.LocalFolder (LocalState), alongside the
177177
* crash/panic/lastcalls logs. NOTE: earlier builds assumed a UWP app's GetTempPathW() virtualises
178178
* to AC\Temp, but the app is `runFullTrust` (not an app container), so GetTempPathW() resolves to
179-
* the *system* temp — which is neither AC\Temp nor a path the CLI could reliably find. The runtime
179+
* the *system* temp, which is neither AC\Temp nor a path the CLI could reliably find. The runtime
180180
* now targets LocalState explicitly (runtime_set_local_folder → set_log_dir), so pin that here.
181181
* Falls back to the system temp path when no PFN is known (unpackaged EXE).
182182
*/
@@ -230,7 +230,7 @@ export class WindowsApplicationManager extends ApplicationManagerBase {
230230
// Truncate the trace log so the streamer starts from a clean state each run.
231231
try {
232232
fs.writeFileSync(this.getLogFilePath(), "", "utf8");
233-
} catch { /* ignore — log dir may not exist yet */ }
233+
} catch { /* ignore: log dir may not exist yet */ }
234234

235235
if (isExe) {
236236
if (appData.waitForDebugger) {

‎lib/common/mobile/windows/windows-device-file-system.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export class WindowsDeviceFileSystem implements Mobile.IDeviceFileSystem {
102102
_hashes: IStringDictionary,
103103
_appIdentifier: string,
104104
): Promise<void> {
105-
// no-op — Windows LiveSync does not use hash-based diffing yet
105+
// no-op: Windows LiveSync does not use hash-based diffing yet
106106
}
107107

108108
public getDeviceHashService(_appIdentifier: string): any {

‎lib/common/mobile/windows/windows-device.ts‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export class WindowsDevice implements Mobile.IDevice {
5656
}
5757

5858
// For packaged UWP apps, GetTempPath() inside the app container resolves to
59-
// %LOCALAPPDATA%\Packages\<PFN>\TempState — not the system temp dir.
59+
// %LOCALAPPDATA%\Packages\<PFN>\TempState: not the system temp dir.
6060
// Ask the application manager for the correct path based on the known PFN.
6161
const manager = this.applicationManager as WindowsApplicationManager;
6262
const logPath = manager.getLogFilePath();
@@ -73,7 +73,7 @@ export class WindowsDevice implements Mobile.IDevice {
7373
// Rotate the log if it exceeds 10 MB to prevent unbounded disk growth.
7474
const MAX_LOG_BYTES = 10 * 1024 * 1024;
7575

76-
// Internal Rust runtime diagnostics written via debug_output() — useful in
76+
// Internal Rust runtime diagnostics written via debug_output(). Useful in
7777
// VS Output / DebugView but noisy in the CLI. Suppress them; errors/exceptions
7878
// are kept because their prefix contains "error", "exception", or "PANIC".
7979
const INTERNAL_PREFIXES = [
@@ -106,11 +106,11 @@ export class WindowsDevice implements Mobile.IDevice {
106106
if (stat.size > MAX_LOG_BYTES) {
107107
try { fs.writeFileSync(logPath, "", "utf8"); offset = 0; } catch { /* ignore */ }
108108
}
109-
} catch { /* ignore — file may not exist between app restarts */ }
109+
} catch { /* ignore: file may not exist between app restarts */ }
110110
}, 50);
111111

112112
// Also tail nativescript-crash.log from LocalState so C# exception reports
113-
// and JS errors caught by the host surface in the CLI — same as Android/iOS
113+
// and JS errors caught by the host surface in the CLI. Same as Android/iOS
114114
// crash log streaming. Truncate on each run so only errors from this session appear.
115115
if (crashLogPath) {
116116
try { fs.writeFileSync(crashLogPath, "", "utf8"); } catch { /* may not exist yet */ }
@@ -134,7 +134,7 @@ export class WindowsDevice implements Mobile.IDevice {
134134
this.$deviceLogProvider.logData(`[crash] ${line}`, "Windows", deviceId);
135135
}
136136
}
137-
} catch { /* ignore — file may not exist until first crash */ }
137+
} catch { /* ignore: file may not exist until first crash */ }
138138
}, 50);
139139
}
140140
}

‎lib/device-path-provider.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export class DevicePathProvider implements IDevicePathProvider {
6262
if (options.getDirname) {
6363
return buildOutputPath;
6464
}
65-
// Sync into bin\app — the registered package root — so the running app
65+
// Sync into bin\app (the registered package root) so the running app
6666
// picks up changes without a rebuild.
6767
return path.join(buildOutputPath, APP_FOLDER_NAME);
6868
}

‎lib/services/bundler/bundler-compiler-service.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,7 @@ export class BundlerCompilerService
597597
USER_PROJECT_PLATFORMS_ANDROID_MODULE:
598598
this.$options.hostProjectModuleName,
599599
USER_PROJECT_PLATFORMS_IOS: this.$options.hostProjectPath,
600+
USER_PROJECT_PLATFORMS_WINDOWS: this.$options.hostProjectPath,
600601
});
601602
}
602603

‎lib/services/livesync/windows-device-livesync-service.ts‎

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as fs from "fs";
2+
import { spawnSync } from "child_process";
23
import { DeviceLiveSyncServiceBase } from "./device-livesync-service-base";
34
import { IPlatformsDataService } from "../../definitions/platform";
45
import { IProjectData } from "../../definitions/project";
@@ -39,15 +40,31 @@ export class WindowsDeviceLiveSyncService
3940
_projectData: IProjectData,
4041
liveSyncInfo: ILiveSyncResultInfo,
4142
): Promise<boolean> {
42-
return !liveSyncInfo.useHotModuleReload;
43+
return !liveSyncInfo.useHotModuleReload || !!liveSyncInfo.waitForDebugger;
4344
}
4445

4546
public async tryRefreshApplication(
46-
_projectData: IProjectData,
47-
_liveSyncInfo: ILiveSyncResultInfo,
47+
projectData: IProjectData,
48+
liveSyncInfo: ILiveSyncResultInfo,
4849
): Promise<boolean> {
49-
// HMR not yet implemented for Windows — signal full restart
50-
return false;
50+
// Vite HMR: a running app pulls every change from the dev server over its WebSocket
51+
// (HTTP ES modules), so a synced file never needs a restart. But an app that isn't
52+
// running yet (first sync after install) must still be launched. Webpack HMR isn't
53+
// implemented for Windows: signal a restart.
54+
return (
55+
projectData.bundler === "vite" &&
56+
!!liveSyncInfo.useHotModuleReload &&
57+
this.isAppRunning(projectData.projectName)
58+
);
59+
}
60+
61+
private isAppRunning(processName: string): boolean {
62+
const result = spawnSync(
63+
"tasklist",
64+
["/FI", `IMAGENAME eq ${processName}.exe`, "/NH", "/FO", "CSV"],
65+
{ encoding: "utf8", windowsHide: true },
66+
);
67+
return (result.stdout || "").toLowerCase().includes(`"${processName.toLowerCase()}.exe"`);
5168
}
5269

5370
public async removeFiles(

‎lib/services/project-cleanup-service.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ export class ProjectCleanupService implements IProjectCleanupService {
172172
try {
173173
execSync(`taskkill /F /IM "${name}.exe" /T`, { stdio: "ignore" });
174174
this.$logger.trace(`[clean] Killed process: ${name}.exe`);
175-
} catch { /* process not running — ignore */ }
175+
} catch { /* process not running: ignore */ }
176176
}
177177
}
178178
}

0 commit comments

Comments
 (0)