diff --git a/content/guide/pathways.md b/content/guide/pathways.md new file mode 100644 index 00000000..4b28299e --- /dev/null +++ b/content/guide/pathways.md @@ -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/). diff --git a/content/guide/publishing/compiled-android.md b/content/guide/publishing/compiled-android.md new file mode 100644 index 00000000..14f7a6ca --- /dev/null +++ b/content/guide/publishing/compiled-android.md @@ -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 `). + +## 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. diff --git a/content/guide/publishing/compiled-configuration.md b/content/guide/publishing/compiled-configuration.md new file mode 100644 index 00000000..279a5512 --- /dev/null +++ b/content/guide/publishing/compiled-configuration.md @@ -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/+.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/`, so a hook that edits the Xcode or Gradle project under +`platforms/ios` or `platforms/android` has nothing to change. diff --git a/content/guide/publishing/compiled-differences.md b/content/guide/publishing/compiled-differences.md new file mode 100644 index 00000000..5eebe4da --- /dev/null +++ b/content/guide/publishing/compiled-differences.md @@ -0,0 +1,316 @@ +--- +title: 'Compiled releases: what works' +description: The frameworks, apps, plugins and JavaScript a compiled release supports, what the build refuses, and where it still behaves differently from the JavaScript release. +contributors: + - NathanWalker +--- + + + +A [compiled release](/guide/publishing/compiled) runs your TypeScript as Swift and Kotlin, against a runtime that +reproduces JavaScript's semantics. This page lists what is checked, what the build refuses, and where a compiled +release still behaves differently from the JavaScript release. The status is as of 2026-10-15. + +## Frameworks and apps + +Each app is compared with its own JavaScript release build, screenshot by screenshot. + +| App | iOS | Android | +| --------------------------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------- | +| XML + TypeScript gallery | Identical (0 px) | Identical (0 px, minified) | +| Vue gallery | 189 of 191 shots identical; a text caret (timing) and one HtmlView differ | Identical (0 px, minified) | +| Angular gallery | Identical (0 px) | Identical (0 px, minified) | +| Svelte 5 gallery | Identical (0 px) | Identical (0 px, minified) | +| OpenJS app (Angular, eight plugins) | Builds and launches | 47 of 49 shots identical; the other two are run-to-run variance | +| ns-duo-guitar (Angular, WebGPU, audio worker) | On the App Store | On Google Play | + +React, Solid and Octane compile with the same pipeline and are supported; they are not in the comparison runs above. + +### Tested framework versions + +The build stops on a framework version outside these (`release.allowUntestedFrameworks: true` builds anyway, with a +warning). + +| Framework | Packages and versions | +| ------------------------- | --------------------------------------------------------------------------------------------------------------- | +| Vue | `nativescript-vue` 3.1 and later 3.x | +| Angular | `@nativescript/angular` and `@angular/core` 22.x | +| React | `react-nativescript` 5.x with `react` 18.2 or later 18.x | +| Solid | `@nativescript-community/solid-js` and `solid-js` 2.x (from 2.0.0-rc.0) | +| Svelte | `@nativescript-community/svelte-native` 1.x with `svelte` 4.2+, or 5.x (from 5.0.0-alpha.0) with `svelte` 5.50+ | +| Octane | `@nativescript-community/octane` 0.2.4 and later 0.2.x | +| Plain TypeScript with XML | Core's own Builder: `{{ }}` bindings, code-behind, custom components and ListView templates | + +Framework features not supported yet stop the build with a message. The main ones: + +- **Angular:** under zone.js, a component that doesn't set `changeDetection: ChangeDetectionStrategy.Default`; + pipes other than `async`; a pipe inside `*ngFor`/`@for`; more than one `@Component` in a file. RxJS is a subset. +- **React:** a component body may hold function declarations, variable declarations and the `return`; a bare + statement such as `useEffect(() => …, [])` on its own line is refused. JSX spread attributes are refused. +- **Vue:** `