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.
  • Your code does not change. The compiler reads the TypeScript you already have, the framework you already use, and your plugins' own TypeScript source.
  • What it cannot compile, it refuses. Anything it does not support stops the build with the file, the line and what to do, instead of shipping something that behaves differently.
  • It is checked against the release it replaces. Proof apps match their JavaScript Release builds pixel for pixel, screen by screen and after the same taps.

What you get, measured on real apps:

AppJavaScript releaseCompiled release
Recipes (Vue), .ipa14.0 MB0.45 MB
Recipes (Vue), memory at launch, iPhone 16 Pro45.1 MB15.6 MB
Recipes (Vue), first frame, iPhone 16 Pro227 ms129 ms
ns-octane (6 plugins), .ipa14.4 MB0.96 MB
Recipes, Android APK104 MB (4 ABIs)0.9 MB
openjs-app (App Store app), installed on iPhone79.6 MB5.4 MB

Using it ​

Install ​

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

Requirements:

  • Node.js 20 or newer, as for the CLI.
  • iOS: Xcode. 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: the Android SDK. The compiler ships its own Gradle wrapper. Windows is not supported yet.

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
Pixel-identical to the JavaScript releaseRecipes in six frameworks; gallery (40 screens); ns-octane; openjs-app's tabsRecipes in six frameworks; gallery (169 of 171 shots); ns-octane
Verified onSimulators and an iPhone 16 ProEmulators
NativeScriptKitGenerated from coreHand port of core, on core's own org.nativescript.widgets
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:

  • Inherently: eval, new Function, loading code at run time and over-the-air JavaScript updates. There is no JavaScript to evaluate or replace. Apps that depend on these keep the JavaScript release.
  • Android: a core property the hand-ported kit does not apply yet. The build names the view, the property and the line. release: { allowUnimplementedProperties: true } builds anyway, with a warning for each. On iOS every core property applies, as the kit is core.
  • 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, symbol-named members, 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
A core property the compiled build does not apply yetFix it in the kit, or allowUnimplementedProperties: true to ship without it
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' },
  allowUnimplementedProperties: 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). If something cannot be compiled, the build stops and names the file and line, so you never find out from a user.

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. 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 applies the line table:

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.

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. The development runtime and the release are still 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, so every core release, fix and platform-version branch reaches compiled releases without a port. On iOS the generated kit runs the proof apps; Android still uses the hand port.

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.
  • Android: the kit generated from core (in progress), WinterTC globals, and real-device verification.
  • fetch and XMLHttpRequest; localization checked against core.
  • Uploading the compiled code's TypeScript line maps to crash reporters.

See also ​

Previous
Publishing