Repository navigation
RFC: Windows support #61
triniwiz
started this conversation in
1. Ideas & Discussions
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
RFC: Windows support
@nativescript/windows), feat: apply webpack HMR updates while a debug app runs windows#32Summary
Add Windows as a first-class NativeScript platform, next to Android, iOS and visionOS.
A NativeScript Windows app is a WinUI 3 desktop app built on the Windows App SDK. JavaScript gets
direct access to every Windows Runtime (WinRT) API, every WinUI API and any .NET type, the same way
it reaches Java on Android and Objective-C on iOS.
@nativescript/corerenders its UI with nativeWinUI controls, so an existing NativeScript app runs on Windows with no code changes.
The work spans four projects:
@nativescript/windows): a new runtime, in https://github.com/NativeScript/windows.windowsplatform forns platform add,prepare,build,run,debug,typingsanddoctor, with MSIX packaging.*.windows.tsimplementation of every core module, plusisWindows,__WINDOWS__and a
windowsgetter whereveriosandandroidgetters exist.App_Resources/Windowsfolder in every app template.Basic example
Adding the platform and running an app works the same way it does on Android and iOS:
Platform code works as it does on the other platforms. Platform-specific files use the
.windowssuffix, and
isWindows/__WINDOWS__guard code that only runs on Windows:WinRT async operations convert to promises with
NSWinRT.toPromise, and events are assigned likeDOM
on*handlers:C# sources in
App_Resources/Windows(or a plugin'splatforms/windows) are compiled into the appand usable from JavaScript by namespace, with no registration step:
WinRT and .NET classes can be extended from JavaScript, as Java and Objective-C classes can:
Motivation
NativeScript has supported the two mobile platforms since 2015, and visionOS since 8.6. Windows
support was first requested in 2015 (NativeScript/NativeScript#254, then targeting UWP) and the
issue is still open. Windows is the most common desktop OS, and teams that ship a NativeScript
mobile app today have to build their Windows app with a second stack (Electron, .NET MAUI, React
Native for Windows, or plain WinUI).
The goals are the same ones that define NativeScript on the other platforms:
third-party
.winmdcomponent, and any .NET assembly must be callable without writing bindings.New Windows SDK APIs must be usable the day they ship.
text rendering, theming (light, dark, high contrast) and the Fluent look.
@nativescript/coremust runon Windows with no code changes, and a JS-only plugin must need nothing to support Windows.
ns run,ns debug, LiveSync, HMR,App_Resources,.windows.tsfiles,native-api-usage.jsonand pluginplatforms/<name>folders must behave the way they do onAndroid and iOS.
submitted to the Microsoft Store.
Desktop adds requirements mobile does not have, and they shape parts of the design below: windows
resize continuously, an app can have several windows, a window can be closed while the app keeps
running, and there is a mouse, a keyboard and multiple displays with different scale factors.
Detailed design
Packages and repositories
@nativescript/windowsnativescript.dll, x64 and arm64), the WinUI 3 host project template, the .NET bridge and the build tools (typings generator, static binding generator, manifest merger, bundle packer).@nativescript/windows-v8,-hermes,-quickjs,-jscpackages/)@nativescript/windows, as@nativescript/android-jscis for Android.@nativescript/core*.windows.tsmodules, theNativeScript.Widgetsnative component, Windows typings intypes-minimal, webpack and Vite support.nativescriptwindowsplatform.App_Resources/Windows.The runtime is versioned on its own, like
@nativescript/androidand@nativescript/ios. It is inpublic beta today (
1.0.0-beta.13on npm). Core declares the minimum runtime it needs inpackage.json, as it does for the other platforms ("windows": "9.1.0"innativescript.platforms).Requirements: Windows 10 version 1809 (build 17763) or later, x64 or arm64, the .NET 10 SDK to
build, and Developer Mode to run debug builds. Visual Studio is not required: the CLI builds with
dotnet build. Windows apps can only be built on Windows.Runtime
Host
The generated platform project is a C# WinUI 3 app (
net10.0-windows10.0.26100.0, Windows AppSDK 1.6). Its
RuntimeHostP/Invokes a small C ABI exported bynativescript.dll(
runtime_init,runtime_runscript,runtime_pump_timers,runtime_notify_app_event, ...). Theruntime itself is written in Rust. The default build embeds V8; the engine variants implement the
same ABI over Node-API, so the host project is identical for every engine.
JavaScript runs on the UI thread, driven by that thread's
DispatcherQueue. Timers, promises andmicrotasks drain on the same queue. Workers are supported, each on its own thread with its own
dispatcher. WinRT and .NET callbacks raised on a background thread are delivered on the JS thread
that registered them, and their return values and exceptions flow back to the native caller.
Metadata
Android and iOS generate metadata at build time. Windows does not need to: every WinRT API is
described by
.winmdfiles that the OS and the Windows App SDK already ship. The runtime resolvestypes from them lazily, the first time JavaScript touches a namespace or a class, and caches the
result. A plugin's or app's own
.winmdfiles are registered at startup, so third-party WinRTcomponents (WebView2, for example) work the same way.
App_Resources/Windows/native-api-usage.jsonlimits the API surface as it does on Android and iOS,with the same pattern syntax and the same whitelist/blacklist rules. Because Windows resolves types
at run time, the filter is enforced where JavaScript reaches a type by name: a filtered type is
undefined, while objects of filtered types returned by allowed APIs still work.JavaScript surface
Windows,MicrosoftandSystemare globals, and so is the root namespace ofany app or plugin C# source or referenced assembly.
Windows.Storage.StorageFileis theStorageFileruntime class.file.Path,FileIO.ReadTextAsync), as iOSkeeps Objective-C selectors. Overloads are selected by argument count.
Size,Color,Thickness) are plain objects with their field names. Enums are numbers, available asNamespace.EnumName.Member.IReference<T>accepts and returnsnulland plain numbers. Valuesboxed in
Objectmembers (Tag,PropertySet) read back as JS primitives.NSWinRT.toPromise(op)turns anIAsyncAction,IAsyncOperation<T>or one oftheir
WithProgressvariants into a promise that resolves to the operation's result and rejectswhen it fails or is cancelled. The conversion is explicit on purpose: operations are not
thenable, so one can be stored, returned from an
asyncfunction or passed back to a native APIas an operation, with its own members (
Status,Cancel(),Progress) intact. .NETTaskresults are awaitable directly.
Append,GetAt,Size), and anIBuffercan beviewed as an
ArrayBufferwithout a copy.el.Loaded = handler), assigningnullunsubscribesand reading returns the current handler. Static events use the same syntax.
Func<>orAction<>isexpected.
class ... extends,@NativeClass,Class.extend()or TypeScript's ES5 output; interfaces are implemented withnew Interface({...})or@Interfaces. XAML calls JS overrides (MeasureOverride,OnApplyTemplate) like any other subclass. Extending .NET types relies on a static bindinggenerator that emits C# proxies at build time, as Android's SBG does for Java.
instance only native code holds keeps working, and UI subclasses in the visual tree keep their
JS fields.
.NET
The host already runs on .NET, so .NET types are reachable directly, through a bridge
(
DotNetBridge) that the runtime loads in-process. Apps are framework-dependent: the .NET DesktopRuntime is a prerequisite on the user's machine, like the Windows App Runtime, and is not bundled
into the package. An app that wants to ship its own copy can publish self-contained through
App_Resources/Windows/app.csproj. C# files inApp_Resources/Windowsand in eachplugin's
platforms/windowsare compiled into the app, and NuGet packages can be referenced fromApp_Resources/Windows/app.csprojor a plugin'splugin.props. This is the Windows counterpart ofJava/Kotlin sources and Gradle dependencies on Android, or Swift sources and CocoaPods on iOS.
Engines
@nativescript/windowsuses V8. The@nativescript/windows-<engine>packages provide the sameframework on V8 over Node-API, Hermes, QuickJS and JavaScriptCore. An app switches by changing
the dependency and rebuilding. The CLI recognises all five package names as the Windows runtime.
The engine variants exist for size (QuickJS and Hermes builds are a fraction of V8's) and for teams
that standardise on one engine across platforms. This RFC does not propose a default other than
V8.
Debugging
Debug builds include the Chrome DevTools inspector.
ns debug windowsstarts the app with theinspector on
127.0.0.1:43000(or the next free port), and supports--debug-brkand--startas on the other platforms. Console output, uncaught JS errors and native crash diagnostics are
written to the app's
LocalStatefolder, which the CLI tails.Source protection
Windows apps ship JavaScript to a desktop file system where it is easy to read. Release builds can
seal the bundler output into an encrypted
app.nsbundleinstead of plain files, opted into withwindows.sourceProtectinnativescript.config.tsor--source-protecton the command line. Itraises the bar for casual inspection; it is not DRM, since the key material ships in the app.
CLI
Platform project
ns platform add windowsinstalls the runtime package and copies itsframework/intoplatforms/windows, naming the project folder after the app:Prepare
ns prepare windowsruns the bundler (platforms/windows/<ProjectName>/appis the output path forboth webpack and Vite), then:
App_Resources/<Platform>when the app has none, by copying it from the defaulttemplate, so existing apps can add Windows without hand-copying files. The CLI used to do this
only when the whole
App_Resourcesfolder was missing; it now does it per platform, for everyplatform.
App_Resources/Windowsinto the platform project and itsAssetsinto the project'sAssets, honouringappResourcesPath.App_Resources/Windows/Package.appxmanifest: the package identity comes fromid(or
windows.id), placeholderPhoneProductId/PhonePublisherIdGUIDs are replaced with generatedvalues, and missing required attributes are filled in. The app's manifest is merged into the
template's at build time.
--phone-product-id,--phone-publisher-id,--regen-guidand--no-auto-guidcontrol the GUIDs.
platforms/windowsfolder intoplugins/<name>, rebuilt from scratch onevery prepare so deleted files and uninstalled plugins disappear, and writes aggregate
Plugins.props/Plugins.targetsthat the host project imports.native-api-usage.jsonout aswhitelist.mdg/blacklist.mdg, as it does for the otherplatforms; the build copies them next to the exe.
Build and deploy
The CLI builds with
dotnet build. There is no emulator: the local machine is the only device, andns deviceslists it.Add-AppxPackage -Register(which needs Developer Mode) and launches it through its packagefamily name.
platforms/windows/<ProjectName>/AppPackages:--certificate <pfx>with--certificate-password, or--certificate-thumbprint, signs asideloadable
.msix;--store-uploadproduces an unsigned.msixuploadfor Partner Center, which re-signs it;--msixbundleproduces a.msixbundle;--arch x64|arm64selects the architecture. The default isx64(64-bit x86);arm64is opt-in.--copy-toworks as on the other platforms.LiveSync and HMR
HMR works with both bundlers, on every engine.
bin\appandthen writes an
ns-livesyncmarker into the app's LocalState. Debug hosts watch for it and callglobal.__onLiveSync()on the UI thread, which is the hook the Android and iOS runtimes callafter a LiveSync, so webpack's HMR runtime applies the update in place. When a hot update fails
(an entry module that is not hot-acceptable, for example), the CLI restarts the app, as on the
other platforms. A host writes
ns-livesync-readywhile it watches, so a CLI paired with anolder host falls back to restarting.
Other commands
ns doctorchecks for Windows, the .NET SDK version and Developer Mode.ns typings windowsgenerates TypeScript declarations intotypings/windowsfrom the WindowsSDK, the Windows App SDK, referenced
.winmdfiles and .NET assemblies (--lib,--libs,--input,--root,--rootsselect extra inputs).ns build|run|debug windowsare Windows-only; their help says so on macOS and Linux.Core
Platform detection
__ANDROID__/__IOS____WINDOWS__isAndroid/isIOSisWindowsplatformNames.androidplatformNames.windows('Windows')view.android/view.iosview.windows, the nativeMicrosoft.UI.XamlelementApplication.android/Application.iosApplication.windows(WindowsApplication)<android>/<ios>in XML<windows>*.android.ts/*.ios.ts*.windows.tsconfig.androidconfig.windowsisWindowsreadstypeof __WINDOWS__ !== 'undefined', so code compiled by a bundler that predatesthis RFC still evaluates it to
false.UI
Every core module has a
*.windows.tsimplementation: application, file system, HTTP, connectivity,application settings, image source and cache, timers, fonts, color, dialogs, gestures, animations,
transitions and all views and layouts. Views map to WinUI controls (
ButtontoButton,TextFieldto
TextBox,ListViewtoListView,DatePickertoDatePicker, and so on).Layout differs from the other platforms. On Android and iOS core measures and positions every view
in JavaScript. On Windows each layout container is a XAML panel, and XAML's measure and arrange pass
positions the children:
GridLayoutis a XAMLGrid,StackLayoutandFlexboxLayoutare nativeC++/WinRT panels, and
AbsoluteLayout,DockLayoutandWrapLayoutplace children on aCanvas.Core still implements
measure/layout/onMeasureso custom views and plugins that call themwork, but the common containers lay out in native code, which matters for resizable desktop windows. A parent with its own
layout engine can take over its children's percentage sizes through
View._setChildPercentSize(child, horizontal, fraction)(Windows only, NativeScript/NativeScript#11468).The panels and helpers XAML lacks (flexbox, CSS-style borders and shadows, clipping, tiled
backgrounds, text shadows) live in
NativeScript.Widgets, a small C++/WinRT component inpackages/ui-mobile-base/windows. It plays the rolewidgets-release.aarplays on Android, andships the same way: prebuilt for x64 and arm64 in core's
platforms/windows, as the Android widgetsare in
platforms/android. Itsplugin.targetscopies the right architecture into the app andregisters its activatable classes in the app manifest. Apps never list them by hand.
CSS is applied through the same style system. Backgrounds, borders (including per-side colours and
radii),
box-shadow,clip-path, gradients and fonts are rendered with WinUI brushes and theComposition API.
Desktop behaviour
NativeWindow(feat: add cross-platform NativeWindow implementation NativeScript#11181) is backed byMicrosoft.UI.Xaml.Window. An app can open several windows;Screenmetrics and the app rootfollow the primary window, and the app ends once its last window closes.
suspend/resumeandbackground/foregroundfire when the app as a wholemoves between active and inactive, not when focus moves between its own windows.
@mediaqueries are evaluated against the window a view is in, not the mainscreen, and re-evaluated as the window resizes. File-name screen qualifiers
(
main-page.minWH600.xml,.land/.port) are re-resolved when the window crosses a threshold,and the current page is swapped for the matching variant.
getLocationInWindow,getLocationOnScreenandgetLocationRelativeToreturnDIPs as on the other platforms, accounting for the window's position and scale
(feat(windows): view location in window, on screen and relative to a view NativeScript#11515).
Typings
Windows typings follow the existing split:
@nativescript/types-minimalholds the declarations core compiles against, for the latest SDKonly. It gains
lib/windows: the Windows SDK (windows.d.ts), WinUI and the Windows App SDK(
microsoft.ui.d.ts),NativeScript.Widgetsand the runtime's helpers.@nativescript/types-windows, next totypes-iosandtypes-android, holds the fulldeclarations for apps, and
@nativescript/typesdepends on it.All of them are generated by the runtime's typings generator. Apps can generate declarations for
their own
.winmdand .NET inputs withns typings windows.Bundlers
@nativescript/webpackgets awindowsplatform that defines__WINDOWS__and emits into theWindows project's
appfolder.@nativescript/vitesupports thewindowsplatform in build modeand in HMR mode, including
.windowsfile resolution and the per-platform global defines.Changes to shared code
A few changes in the core PR touch code all platforms run, and are worth reviewing on their own:
checkIfMediaQueryMatchesandmatchMediaQueryStringtake an optional window context, and theselector scope passes the node's window. Without one they behave as today.
var()fallbacks may contain commas and nested parentheses, custom properties may refer to onesdeclared after them, and a declaration that fails to apply resets the property instead of leaving
the previous value in place (CSS's "invalid at computed-value time" rule). These are under
review on their own in fix(core): css var() fallbacks, order-independent custom properties, and no crash on unresolvable ones NativeScript#11527.
Templates
Every template gets
App_Resources/Windows, through the sharedshared-mobileresources:Package.appxmanifestwith__APP_IDENTIFIER__and__PROJECT_NAME__placeholders, the WindowsApp Runtime dependency, and placeholder phone identity GUIDs the CLI fills in;
app.csproj, the app's MSBuild hook (NuGet references, properties), imported by the host project;app.manifest(per-monitor DPI awareness, supported OS);Templates do not add
@nativescript/windowstopackage.json;ns platform add windowsor thefirst
ns run windowsinstalls it, as for the other runtimes.Plugins
A plugin supports Windows the way it supports the other platforms:
The CLI copies everything except JS, TS, JSON, map and markdown files. When a plugin provides no
plugin.props/plugin.targetsthe CLI generates them, copying the plugin's binaries into the appoutput. JS-only plugins need nothing. A plugin declares its minimum runtime under
nativescript.platforms.windowsinpackage.json, as core does.Drawbacks
implementation to keep in step. The Windows views are a large body of new code (the core PR adds
about 16,000 lines of Windows modules and native widgets, outside generated typings) and there
are no automated specs for the Windows views yet.
controls native, but it means Windows can disagree with Android and iOS in edge cases that the
shared JS layout code would have handled identically. Fixes for those land in Windows-specific
code, not in the shared layout.
types-minimal, and core carries prebuiltNativeScript.Widgetsbinaries for twoarchitectures, as it already does for the Android widgets.
implementation. Until they have one, apps that depend on them cannot target Windows.
Windows runners.
in addition to the Windows App Runtime unless the app opts into a self-contained publish.
yet.
Alternatives
Windows App SDK, and UWP apps are sandboxed in ways that make many desktop use cases impossible.
WinUI 3 runs as a full-trust desktop app and still uses MSIX for packaging and the Store.
current Fluent controls, and no metadata format the runtime could read the way it reads
.winmd.of NativeScript's native-UI model.
SDKs, so reading it at run time avoids a generation step and makes new SDK APIs available
immediately. The cost is a lazy lookup on first use of each type, which is cached.
layout identical across platforms, but every window resize would run the whole layout pass in JS
while working against XAML's own layout system.
the runtime could drop the classic V8 build. The classic build is the most mature and is what
apps have been tested on, so this RFC keeps it as the default and leaves the variants opt-in.
The same code also builds as a Node-API addon (
@nativescript/windows-napi) that exposes WinRTto Node, Bun and Deno; that is a separate use case and not part of this RFC.
Windows.
Adoption strategy
This is additive and not a breaking change. Apps and plugins that do nothing are unaffected.
on Android and iOS, since bundlers only include
.windows.tsfiles for the Windows platform.@nativescript/windowsstays in beta and is published from the runtime repo's CI(
npm_release.yml), with the engine variants alongside it.ns platform add windows; the CLI addsApp_Resources/Windowsfromthe default template if the app has none. This needs the templates from
feat: windows nativescript-app-templates#244 to be published first.
index.windows.tsandplatforms/windows. Porting the plugins inNativeScript/pluginsearly would give authors working examples..windows.ts, the WinRT/.NET marshaling rules,App_Resources/Windows, signing and Store submission.Unresolved questions
None at the time of writing.
All reactions