NativeScript 9.1 Released → V8 14.9, Vite 8 HMR, built for rapid agentic visual iteration
Dig in

Preview

Compiled releases are in preview. To use, npm install -g nativescript@beta. The --compiled flag works with the beta CLI and the @nativescript/compiler. This page describes how it works and what it supports.

A compiled release is the TypeScript pathway's release (see Two pathways). You develop with ns run|debug exactly as today: using the JavaScript runtime, HMR, Chrome DevTools. When you build the release, the --compiled flag compiles your app to Swift on iOS and Kotlin on Android, and the app ships no JavaScript engine, no bundle and no NativeScript runtime.

bash
ns build ios --compiled

The short version ​

  • One flag. --compiled on ns build, ns run or ns deploy, or release: { compiled: true } in nativescript.config.ts. It implies --release.
  • Most apps compile without source changes. The compiler reads the TypeScript you already have, the framework you already use, and your plugins' own TypeScript source. The apps compiled so far (four proof apps and two store apps) changed no source; some added configuration.
  • What it knows it cannot run, it refuses. Before compiling, a check reports every construct outside the supported subset (Proxy, eval, a computed import(), an undefined bundler constant…) with the file, the line and what to write instead, and the build stops on a core or framework version it was not built for. This is not a proof that the rest behaves identically: the known differences still apply, so check each release against its JavaScript build.
  • It is checked against the release it replaces. Proof apps are compared with their JavaScript release builds screen by screen and after the same taps: on iOS the five galleries match pixel for pixel except one HtmlView screen (189 of 191 Vue gallery shots); on Android the galleries and Recipes match at 0 px. The compiler's differential tests hold the runtime to Node's output: every case on iOS, and on Android all but nine known failures, listed with their causes in the compiler's repository. Core's own unit-test app does not pass compiled yet: on iOS, 647 of its tests pass, 115 fail and 957 do not run.

What you get, measured on real apps (the dates and methods are below the table):

AppJavaScript releaseCompiled release
Recipes (Vue), first frame, iPhone 16 Pro190 ms191 ms
Recipes (Vue), memory footprint after launch, iPhone 16 Pro47.3 MB18.9 MB
Recipes (Vue), .ipa14 MB2.35 MB
ns-duo-guitar (Angular, WebGPU), Play download, arm6428.1 MB14.6 MB
openjs-app (Angular, eight plugins), Play download, arm64, 420 dpi28.6 MB15.2 MB

iOS: 2026-10-09, iOS 26.6.2, cold launch to first frame from screen recordings, median of 5 runs; memory is the physical footprint; the .ipa is unsigned, arm64. Android: 2026-10-10, sizes from the App Bundle for one device configuration (arm64-v8a, 420 dpi, English). The compiled release launches as fast as the JavaScript one, not faster; the gains are memory and size. The scripts that take these measurements, with their method in each one's header, are in the compiler's bench/ folder; they record the compiler commit with every result.

Using it ​

Install ​

bash
npm install --save-dev @nativescript/compiler

@nativescript/compiler is released with @nativescript/core, at core's version: install the compiler version that matches your core (the build checks it). It is not yet published to npm.

Requirements:

  • Node.js 20 or newer, as for the CLI.
  • @nativescript/core at the compiler's version, and a framework version from the tested versions.
  • iOS: Xcode 26 or newer. The Xcode project is generated with XcodeGen: yours if it is installed, otherwise a pinned release the compiler downloads once into ~/Library/Caches/nativescript, checked against its published checksum. The deployment target is at least iOS 17; a lower one in build.xcconfig is raised, and the build says so.
  • Android: JDK 17 or newer and the Android SDK. The compiler ships its own Gradle wrapper. Windows is not supported yet.

The build checks these before compiling, and names what is missing.

Build, run, deploy ​

bash
ns build ios --compiled                   # simulator .app
ns build ios --compiled --for-device      # archive and .ipa; sign with --team-id or --provision
ns run ios --compiled                     # build, install and launch on a simulator or device

ns build android --compiled --key-store-path release.keystore --key-store-password … \
  --key-store-alias … --key-store-alias-password …     # signed .apk; add --aab for a bundle
ns run android --compiled --key-store-path …

Signing works as for any release: on iOS, --team-id signs automatically and --provision signs with a profile (without either, the archive and .ipa are unsigned, with a warning); on Android, the --key-store-* options sign as Android Studio does. The app id is your project's id, so a compiled release installs over the JavaScript build of the same app, and the store steps are the same.

ns run without --compiled, ns debug and every debug build stay on the JavaScript runtime.

Make it the default ​

ts
// nativescript.config.ts
export default {
  id: 'org.example.app',
  release: {
    compiled: true, // every release build is compiled
  },
  android: {
    release: { compiled: false }, // except Android's, for now
  },
} as NativeScriptConfig

--compiled and --no-compiled on the command line win over the config, and a platform's release wins over the top-level one. --no-compiled builds one release on the JavaScript runtime whatever the config says.

The output goes to platforms/compiled/ios and platforms/compiled/android, never platforms/ios or platforms/android: the JavaScript platform projects are left as they are. On iOS the output is an ordinary Xcode project you can open, build and run from Xcode.

How it works ​

text
your app (TypeScript, templates, CSS)            plugins (their TypeScript source)
                     │                                        │
                     └──── one TypeScript program, type-checked against @nativescript/core ────┘
                                                │
                              compiler: Swift (iOS) / Kotlin (Android)
                                                │
                            + NativeScriptKit: @nativescript/core as native code
                            + plugins' native code (platforms/ios, platforms/android), linked unchanged
                                                │
                               Xcode / Gradle ──▶ .app .ipa / .apk .aab
  1. Your framework's own parser reads your templates. Angular, Vue, React, Svelte, Solid and Octane templates are parsed by each framework's own compiler, so what you write means what it means in the framework.
  2. Your styles go through your own build. Your CSS pipeline (Tailwind, PostCSS, Sass) runs as your app's build runs it; the result is applied as core applies CSS. Components' own styles (Vue <style>, including scoped; Angular styles and styleUrl, encapsulated as NativeScript Angular encapsulates them; Svelte <style> where your Svelte configuration injects component CSS) are scoped as their framework scopes them and added in the same order.
  3. Everything is type-checked as one program. The app and its plugins are checked against @nativescript/core's declarations, then translated. JavaScript semantics (numbers, promises and microtasks, closures, Map/Set, iteration order) are reproduced exactly; the compiler's differential tests run each case under Node and as compiled code and require the same output.
  4. Core becomes NativeScriptKit. What @nativescript/core does through the JavaScript runtime, the compiled app does through NativeScriptKit: core's views, layouts, styling, property system and platform APIs as Swift and Kotlin. On iOS, NativeScriptKit is generated from core's own TypeScript by the same compiler, so it follows core release by release (see Under the hood).
  5. Plugins compile from their own TypeScript. For each plugin the compiler fetches the exact source its published version was built from, checks it against the published JavaScript, and compiles it with your app. The plugin's native code (platforms/ios, platforms/android: Swift, Objective-C, .xcframeworks, CocoaPods, Swift packages, Java/Kotlin, Gradle dependencies) is linked unchanged.
  6. App_Resources carry over as the CLI carries them: Info.plist and entitlements merged with plugins', asset catalogs, the launch screen, build.xcconfig, the Android manifest and resources, fonts, and the files your build copies (assets/). Native source in App_Resources/iOS/src is compiled into the app, and app extensions (widgets, Live Activities) become their own targets.

What a compiled release supports ​

Frameworks ​

FrameworkStatus
Vue 3 (<script setup>, Options API)Supported
Angular (standalone with signals; NgModules with zone.js)Supported; ChangeDetectionStrategy.Default components, the async pipe, a subset of RxJS
Svelte 4 and 5Supported
React (react-nativescript)Supported
SolidSupported
OctaneSupported
Plain TypeScript with XMLSupported: core's own Builder makes the views from your XML, as in the JavaScript build: bindings, code-behind, custom components (xmlns:my) and ListView templates

Core's own behavior comes with it on iOS, where the kit is core: RTL layout, Dynamic Type through ios-a11y-adjusts-font-size, font:// icons, background-image: url(), box shadows and the accessibility properties render as in the JavaScript build, pixel for pixel. The web globals the NativeScript runtime provides are there too: crypto (randomUUID, getRandomValues, and subtle with SHA digests, HMAC and RSA-OAEP), TextEncoder/TextDecoder, atob/btoa, timers, queueMicrotask and console. fetch and XMLHttpRequest are not yet available in a compiled release.

Platforms ​

iOSAndroid
Compared with the JavaScript releaseGalleries in five frameworks, pixel for pixel except one HtmlView screen; xml-ts; openjs-app (store app)Galleries (Vue 41 screens, XML, Angular, Svelte 5) and Recipes at 0 px, minified and not; openjs-app 49 shots
Verified onSimulators and an iPhone 16 ProEmulators and a Galaxy Z Fold3; ns-duo-guitar is on Google Play
NativeScriptKitGenerated from coreGenerated from core (the hand port remains behind android.release.generatedKit: false)
Runs oniOS 17 and later; checked on iOS 26 and 27Android 7 (API 24) and later

Plugins ​

Kind of pluginIn a compiled release
TypeScript over native APIs (most plugins)Compiled from the plugin's source, as part of your app
Native code in platforms/ios / platforms/androidLinked unchanged
Installs objects into the JavaScript engine itself (@nativescript/canvas)Needs a native counterpart in the kit. Canvas has one on iOS: 2D, WebGL and WebGPU over the same native library
Only meaningful with a JavaScript runtime (over-the-air JavaScript updates)Replace it with a module of your own: release.pluginReplacements

What stops a build ​

The compiler refuses what it cannot compile faithfully, and says so with the file and line. The full list, with what to write instead, is on Supported subset and known differences.

  • Inherently: eval, new Function, Proxy, Reflect, loading code at run time (a computed import()) and over-the-air JavaScript updates. There is no JavaScript to evaluate or replace. Apps that depend on these keep the JavaScript release. These are found before anything is compiled, all at once.
  • A bundler constant nothing defines, such as declare const __API_URL__ without a define. The build reads Vite's define and webpack's DefinePlugin and compiles their values in.
  • A version mismatch: a @nativescript/core other than the one the compiler's kit was generated from, or a framework version outside the tested ones.
  • Language constructs not supported yet, such as a class declared inside a function that reads the function's own variables (one that reads none compiles), a default clause before other cases, or return in a finally block. Each message names the construct.
  • Framework features not supported yet, such as Angular OnPush under zone.js, Angular pipes other than async, more than one Angular @Component in a file, Vue <style module>, JSX spread attributes, and Vue Options-API keys beyond the common ones.
  • Plugin native code the build does not carry yet: a prebuilt .framework or .a (an .xcframework is fine), a resource .bundle, a .podspec, and Android jniLibs/.so files.

Your project's hooks run as they do for a JavaScript build: before-prepare/after-prepare and before-buildIOS/after-buildIOS (buildAndroid on Android), with the same arguments. In a compiled build projectRoot is the compiled project (platforms/compiled/<platform>), so a hook that edits the runtime's Xcode or Gradle project under platforms/ios or platforms/android has nothing to change there.

What your app may need ​

For most apps: nothing but the flag. Of the apps compiled so far, none changed their own source to compile. What some needed was configuration, all of it in release:

SituationWhat to add
A plugin only meaningful with a JavaScript runtime (OTA updates such as Norrix)pluginReplacements: { '@norrix/client-sdk': './release/norrix.ts' }: a module of your own with the same exports
A plugin whose published source cannot be found or does not match its JavaScriptpluginSources: { 'some-plugin': '../some-plugin' }: a checkout of its source
You patch a plugin with patch-packageThe same patch against the plugin's TypeScript source, in native-release/patches/<package>+<version>.patch. The compiler compiles source, not the published JavaScript your existing patch changes
You patch @nativescript/core with patch-packageThe compiler recognizes common core patches and applies their effect; a core patch it does not recognize stops the build, so you know to raise it
@nativescript/core at another version than the compiler'sInstall the matching versions; allowCoreMismatch: true builds anyway, with a warning
A framework version outside the tested onesallowUntestedFrameworks: true builds anyway, with a warning
An Info.plist that declares no UIApplicationSceneManifest (older app templates)Nothing: the build adds the template's and says so. Apps built with the current iOS SDK must adopt scenes
ts
// nativescript.config.ts: every option
release: {
  compiled: true,
  pluginReplacements: { '@norrix/client-sdk': './release/norrix.ts' },
  pluginSources: { 'some-plugin': '../some-plugin' },
  allowCoreMismatch: false,
  allowUntestedFrameworks: false,
},

How it went for real apps:

  • openjs-app (Angular, on the App Store): no source changes. A stand-in for its OTA update plugin, and source-level copies of two plugin patches.
  • ns-octane (Octane, six plugins): no source changes. Its input-accessory patch was ported to the plugin's source. One Swift file that called into the JavaScript runtime was left out, with a message.
  • ns-duo-guitar (Angular, @nativescript/canvas with WebGPU): no source changes needed to compile. Its AI feature was left out of the release by product choice.

Checking a compiled release ​

Treat a compiled release like any new build: compare it with the JavaScript release before you ship it.

  1. Build both: ns build ios --release and ns build ios --compiled, on the same version of @nativescript/core (the compiler's kit is core at its own version). They use the same app id, so install them on separate simulators, or uninstall between them (installing over a different build keeps stale files).
  2. Walk the same screens and taps in both, focusing and typing in every input, and compare. The proof apps are compared pixel by pixel; differences that come from the platform itself (the clock, animation timing) are expected.
  3. Watch for crashes as well as pixels: a screen can match and still fail on interaction.

ns compiled verify does this for you on an iOS Simulator. It builds both releases, runs the same steps on each (from the project's verify.json: taps, swipes, typing, waits and shots, each screen from a fresh launch), compares every shot pixel by pixel once the screen settles, and fails on a difference or on an app that stops running. The screenshots and a report.json go to platforms/compiled/verify.

json
{
  "screens": [
    { "name": "home", "steps": [["shot", "start"], ["tap", 200, 400], ["wait", 1], ["type", "Ada"], ["shot", "typed"]] }
  ]
}

FAQ ​

Do I have to change my app to use --compiled? ​

Usually not. The compiler reads the TypeScript, templates and CSS you already have. None of the apps compiled so far changed their own source; some added a few lines of release configuration (see What your app may need). What the compiler knows it cannot run stops the build with the file and line. Some differences only show at run time; they are listed on Supported subset and known differences, and checking the release against its JavaScript build finds the ones that matter to your app.

How do I map a crash in a compiled release back to my TypeScript? ​

iOS, today: every compiled statement carries the file and line it came from (a Swift #sourceLocation directive). The archive's dSYM therefore symbolicates crash reports straight to your .ts, .vue, .tsx or .svelte lines. Upload the dSYM to your crash reporter (Sentry, Crashlytics, App Store Connect) as you would for any iOS app, and its frames name your TypeScript. The Xcode debugger shows and steps through the same lines.

Android, today: Kotlin has no such directive, so the build writes a line table (source-lines.json) beside the Gradle project, and the compiler ships one for the kit generated from core. ns-native-retrace reads a stack trace in source lines: it runs R8's retrace with the build's mapping.txt (from the Android SDK's command-line tools), then maps your app's frames to your files and the kit's frames to @nativescript/core files. Archive mapping.txt and source-lines.json with each release you upload. When a compiled build fails in Kotlin, ns build prints each error at the TypeScript line it came from as well.

bash
adb logcat -d -s AndroidRuntime | npx ns-native-retrace platforms/compiled/android
npx ns-native-retrace platforms/compiled/android crash.txt    # or a trace saved from your crash reporter

Expressions from templates map to the component's source file, matched by what they share with it; with a separate template file (Angular's templateUrl) that is the component's .ts, not the .html. The code that builds a component's view tree appears under the component's generated name (for example GuitarComponent.swift), which still tells you which component it was.

error.stack in a compiled release lists the native frames the error was made in. On iOS a frame reads symbol (App+0x1a2b3c); resolve the offset with the archive's dSYM (atos -o App.app.dSYM/Contents/Resources/DWARF/App -l 0x100000000 0x1001a2b3c) to get the TypeScript line. On Android a frame reads class.method(File.kt:line), which ns-native-retrace maps. A crash reporter hooked to JavaScript errors receives these frames, not JavaScript ones.

Planned:

  • One upload for Android crash reporters: the line table composed into mapping.txt, or a standard source map, so reporters show TypeScript lines without a manual step.
  • Template-file lines for template expressions and the view-building code.

Can I debug a compiled release? ​

With Xcode or Android Studio, as a native app: breakpoints, stepping and variables, on iOS against your TypeScript lines. ns debug and Chrome DevTools need the JavaScript runtime, so they stay with development builds.

Will the compiled release behave exactly like the JavaScript one? ​

That is the goal, and it is checked rather than assumed. JavaScript's semantics (numbers, promises and microtask order, closures, iteration order, Map/Set) are reproduced by a runtime that the compiler's differential tests hold to Node's output, and the proof apps match their JavaScript releases pixel for pixel. It is not there yet: the differences known today (among them null and undefined in typed optionals, listener removal on iOS, and some regular-expression classes) are listed on Supported subset and known differences. The development runtime and the release are different engines, so check each release before you ship it (see Checking a compiled release).

Can I keep over-the-air updates (Norrix, CodePush-style)? ​

No. Over-the-air updates replace the JavaScript bundle, and a compiled release has none. Ship updates through the stores, and replace the update plugin with a no-op module through release.pluginReplacements. If over-the-air updates are essential to you, keep releasing on the JavaScript runtime.

Can I use any npm package? ​

Packages are compiled with your app from their source, like plugins, so what matters is what they do: code that uses eval, new Function or loads code at run time cannot be compiled. Widely used libraries are not yet systematically verified; a construct the compiler does not support stops the build with its location.

Can I still write native code? ​

Yes. Swift in App_Resources/iOS/src is compiled into the app and called from your TypeScript as before. Plugins' native code (Swift, Objective-C, .xcframeworks, CocoaPods, Swift packages, Java, Kotlin, Gradle dependencies) links unchanged. App extensions such as widgets and Live Activities become their own targets.

Is the generated Swift and Kotlin meant to be edited? ​

No. It is build output in platforms/compiled/<platform>, regenerated on every build; your TypeScript stays the source of truth. You can open the iOS project in Xcode to run, profile or debug it.

How long does a compiled build take? ​

The compile step takes seconds to tens of seconds, depending on the app (an Angular app with 9 components and 27 modules: about 20 seconds), followed by Xcode's or Gradle's build. Rebuilds after a small change take a few seconds of Xcode or Gradle time.

Which platforms and OS versions? ​

iOS 17 and later, and Android. visionOS, watchOS and other platforms build on the JavaScript runtime.

What if a plugin I use does not compile? ​

The build names the plugin and what stopped it. Depending on the cause: point release.pluginSources at its source, replace it with release.pluginReplacements, or raise it with the plugin's author. Plugins that install objects into the JavaScript engine (such as @nativescript/canvas) need a native counterpart in the kit, which exists for canvas on iOS.

Can I go back? ​

Any time: --no-compiled builds one release on the JavaScript runtime, and removing release.compiled from the config makes it the default again. Nothing in your project depends on the compiled build.

Under the hood ​

Who touches what:

PieceWhat it isWho uses it
The CLI (nativescript)--compiled, the release config, building, signing and installing the compiled projectApp developers
@nativescript/compilerThe compiler, and NativeScriptKit's sources for Swift and KotlinApp developers, as a devDependency
NativeScriptKit@nativescript/core as native code, linked by every compiled appNobody directly; it comes with the compiler
Core's kit generator (tools/native-kit and packages/compiler in the NativeScript repo)Compiles core's TypeScript into NativeScriptKit, once per core release, checked by CI, which builds every file of the kit; the compiler is published at core's versionCore maintainers

NativeScriptKit started as a hand port of core and matched it wherever it was ported, but a hand port drifts everywhere else. It is now generated from core's own TypeScript by the same compiler that compiles apps, on iOS and Android, so every core release, fix and platform-version branch reaches compiled releases without a port. The published compiler carries both kits, generated from the core release it is published with, and CI builds an app from the packed package before publishing.

What is still open ​

  • Publishing @nativescript/compiler and releasing the CLI flag.
  • Core's own apps as compliance: apps/automated (core's unit tests) and apps/toolbox, compiled, run and compared with their JavaScript builds.
  • Plugins: .framework and static libraries, resource bundles, and a way for plugin authors to ship native counterparts of engine-bound code. A corpus of widely used plugins and npm libraries, built in CI.
  • The known differences at run time.
  • Android: WinterTC globals; R8 keeps narrowed to what the app reaches by name.
  • fetch and XMLHttpRequest; localization checked against core.
  • Uploading the compiled code's TypeScript line maps to crash reporters.

See also ​

Previous
Publishing