Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions content/guide/pathways.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
title: 'Development options: TypeScript or SwiftUI'
description: NativeScript is your development runtime, not your production runtime. Write SwiftUI and develop it live, or write TypeScript and compile the release to native code.
contributors:
- NathanWalker
---

NativeScript has always run your app on a JavaScript runtime with direct access to every platform API. It now offers
two development options that keep that experience and ship **no JavaScript runtime at all**:

| | 1. SwiftUI, live | 2. TypeScript, compiled |
| -------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| **You write** | SwiftUI, in an Xcode project | TypeScript with `@nativescript/core` and the framework you like (Angular, Vue, React, Svelte, Solid, plain TypeScript) |
| **While developing** | NativeScript runs your SwiftUI views live: each save is on screen in about 0.15–0.5 s, state kept | `ns run`: the JavaScript runtime with HMR, Chrome DevTools and the whole npm ecosystem, exactly as today |
| **What ships** | Your Swift, built by Xcode as usual | Your app compiled to Swift (iOS) and Kotlin (Android) by `ns build --compiled` |
| **Platforms** | iOS; Android live development, rendered by Jetpack Compose | iOS and Android |
| **Status** | [Preview](/guide/swiftui-live) | [Alpha](/guide/publishing/compiled) |

Both rest on the same idea: **develop live, ship native.** The development runtime gives you instant feedback; the
release is the native code a platform developer would have shipped.

## Which one fits

- **You know SwiftUI, or you are starting an iOS-first app in Swift**: take [SwiftUI, live](/guide/swiftui-live).
You keep Xcode, Swift and your project as they are; NativeScript only shortens the edit-to-screen loop.
- **You know web development, or you already have a NativeScript app**: take
[TypeScript, compiled](/guide/publishing/compiled). Nothing changes while you develop. The release build is
compiled instead of bundled.

The two meet at the release: both ship native code with no engine inside, and both are checked the same way,
pixel by pixel against the build they replace.

## The JavaScript runtime is not going anywhere

A release on the JavaScript runtime (`ns build --release`) remains the default and is fully supported. Compiled
releases are opt-in, per build or per platform, and you can switch back with `--no-compiled` at any time. See
[Publishing](/guide/publishing/).
75 changes: 75 additions & 0 deletions content/guide/publishing/compiled-android.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
---
title: 'Compiled releases: Android'
description: Requirements, signing, threading annotations, R8 and mapping crashes back to TypeScript in a compiled Android release.
contributors:
- NathanWalker
---

## Requirements

- **JDK 17** or newer and the **Android SDK**. The compiler ships its own Gradle wrapper.
- macOS or Linux. Windows is not supported yet.
- The app runs on **Android 7 (API 24)** and later.

## Build, sign, run

A compiled build is a release build, and Android signs every release, so all four `--key-store-*` options are
required:

```bash
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 … # build, install and launch
```

They sign as Android Studio does. To try a build before you have your upload key, make a throwaway keystore (see
[Getting started](/guide/publishing/compiled-getting-started#_2-build-and-run)).

Android installs an app over another only when both are signed with the same key. Moving between a debug build
(signed with the SDK's debug key) and a release signed with another key fails with "a previously installed package
of the same name has a different signature": uninstall first (`adb uninstall <app id>`).

## The Gradle project

`platforms/compiled/android` is a Gradle project, regenerated on every build. App_Resources carry over as the CLI
carries them: `res/`, `AndroidManifest.xml` (merged with plugins'), `app.gradle` and `before-plugins.gradle`, and
fonts. Plugins' Java, Kotlin and Gradle dependencies are linked unchanged.

The kit marks main-thread code `@UiThread` and code that may run on any thread `@AnyThread`, and Android lint
checks calls against them.

`fetch` and `XMLHttpRequest` are not available on Android yet. They are on iOS.

## Minification (R8)

Members reached by name at run time (a method called on an `any` value, `typeof x.getFoo`, `'getFoo' in x`) are
kept from R8's renaming by a scan of the compiled code. A name computed at run time (`x[name]()` with `name` built
from strings) isn't found by the scan: keep such members with a ProGuard rule in
`App_Resources/Android/app.gradle`, and check the minified release on a device.

By default every class and member of the kit is kept. `android: { release: { narrowKitKeeps: true } }` keeps only
what the kit's reflection needs and lets R8 remove the rest: on Recipes (Vue) the kit's dex goes from 3.3 to 2.0 MB
and the APK from 2.44 to 1.92 MB. It is opt-in until it has been checked on larger apps; under it, a kit member
reached only through a computed name is removed.

## Crashes and debugging

Kotlin has no source-location directive, so the build writes a line table (`source-lines.json`) beside the Gradle
project. `ns-native-retrace` reads a stack trace in your TypeScript lines: it runs R8's retrace with the build's
`mapping.txt`, then maps your app's frames to your files and the kit's frames to `@nativescript/core` files.

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

Keep `mapping.txt` and `source-lines.json` with each release you upload. When the Kotlin compile fails, `ns build`
prints each error at the TypeScript line it came from.

`error.stack` lists native frames, such as `class.method(File.kt:line)`, which `ns-native-retrace` maps. Uncaught
errors go to `Application.uncaughtErrorEvent` and are logged as `***** Fatal JavaScript exception *****` (logcat
tag `JS`). Debug a compiled release in Android Studio, as a native app.

## Build times

Measured on a blank app with R8, 2026-10-13: the first build takes a minute or two, a one-line change about 45 s.
66 changes: 66 additions & 0 deletions content/guide/publishing/compiled-configuration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
title: 'Compiled releases: configuration'
description: The release options in nativescript.config.ts, how the flag and the config combine, and what an app may need to add.
contributors:
- NathanWalker
---

## Turning it on

`--compiled` on `ns build`, `ns run` or `ns deploy` compiles that release, and implies `--release`. To make it the
default, set `release.compiled` in `nativescript.config.ts`:

```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
},
} as NativeScriptConfig
```

- `--compiled` and `--no-compiled` win over the config.
- `ios.release` and `android.release` merge over the top-level `release`; object options merge by key.
- Debug builds always run on the JavaScript runtime.

The compiler reads `nativescript.config.ts` without running it: it takes the object literal after `export default`
(an `as` or `satisfies` is fine). A value computed in code, or a config exported through a variable, isn't seen.

## Options

All of them go in `release`, or in a platform's `release`.

| Option | What it does |
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `compiled` | Release builds are compiled instead of bundled for the JavaScript runtime. |
| `pluginReplacements` | `{ 'package': './path.ts' }`: replaces a package with a module of yours that has the same exports. For plugins that only make sense with a JavaScript runtime, such as over-the-air updates. |
| `pluginSources` | `{ 'package': '../checkout' }`: compiles a plugin from a local checkout of its source instead of fetching the published revision. |
| `allowUnimplementedProperties` | Builds even when the app sets a core property the compiled build doesn't apply, with a warning for each. |
| `allowCoreMismatch` | Builds with a `@nativescript/core` other than the one the compiler was made from, with a warning. |
| `allowUntestedFrameworks` | Builds with a framework version outside the [tested ones](/guide/publishing/compiled-differences#tested-framework-versions), with a warning. |
| `narrowKitKeeps` | Android: R8 keeps only what the kit's reflection needs. See [Android](/guide/publishing/compiled-android#minification-r8). |
| `generatedKit` | Android: `false` builds on the older hand-written kit instead of the one generated from core. |

## What your app may need

Most apps need nothing but the flag. Of the apps compiled so far, none changed their own source; some added
configuration:

| Situation | What to do |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| A plugin that only makes sense with a JavaScript runtime (over-the-air updates) | `pluginReplacements: { '@norrix/client-sdk': './release/norrix.ts' }` |
| A plugin whose published source can't be found, or doesn't match its JavaScript | `pluginSources: { 'some-plugin': '../some-plugin' }` |
| You patch a plugin with patch-package | Write the same patch against the plugin's TypeScript source, in `native-release/patches/<package>+<version>.patch`. The compiler compiles the source, not the published JavaScript your patch changes. |
| You patch `@nativescript/core` with patch-package | The compiler recognizes common core patches and applies their effect. One it doesn't recognize stops the build. |
| An `Info.plist` with no `UIApplicationSceneManifest` (older templates) | Nothing: the build adds the template's and says so. |

## Hooks

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 `platforms/compiled/<platform>`, so a hook that edits the Xcode or Gradle project under
`platforms/ios` or `platforms/android` has nothing to change.
Loading
Loading