# @solidtv/nativescript

> SolidTV on NativeScript: the renderer host, key bridge and build helper for Apple TV and iOS

Latest version **0.1.2** (published 2026-09-23) · Apache-2.0 license · 0 weekly downloads

## Install

```sh
npm install @solidtv/nativescript
pnpm add @solidtv/nativescript
yarn add @solidtv/nativescript
bun add @solidtv/nativescript
```

## Health

**Score 70/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; recently updated; high maintenance score; high quality score.

Warnings: low downloads; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.1.2 |
| Published | 2026-09-23 |
| First published | 2026-09-18 |
| Weekly downloads | 0 |
| License | Apache-2.0 |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 4 |
| Unpacked size | 189 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | Chris Lorenzo |
| Maintainers | chiefcll |
| Keywords | solidtv, nativescript, tvos, apple-tv, lightning, webgl |

## Links

- npm: https://www.npmjs.com/package/@solidtv/nativescript
- Repository: https://github.com/solid-tv/nativescript
- Homepage: https://github.com/solid-tv/nativescript#readme
- Issues: https://github.com/solid-tv/nativescript/issues
- npm.io page: https://npm.io/package/@solidtv/nativescript

## Dependencies (4)

- [@babel/core](https://npm.io/package/@babel/core.md) ^7.29.7
- [babel-loader](https://npm.io/package/babel-loader.md) ^10.1.1
- [babel-preset-solid](https://npm.io/package/babel-preset-solid.md) ^1.9.15
- [@babel/preset-typescript](https://npm.io/package/@babel/preset-typescript.md) ^7.29.7

## Alternatives

- [@sindresorhus/slugify](https://npm.io/package/@sindresorhus/slugify.md) — 3.7M weekly downloads
- [solid-js](https://npm.io/package/solid-js.md) — 2.7M weekly downloads
- [expo-glass-effect](https://npm.io/package/expo-glass-effect.md) — 2.5M weekly downloads
- [nanoassert](https://npm.io/package/nanoassert.md) — 780.8K weekly downloads
- [@ffmpeg/ffmpeg](https://npm.io/package/@ffmpeg/ffmpeg.md) — 529.5K weekly downloads

## Recent versions

- 0.1.2 (latest) — 2026-09-23
- 0.1.1 — 2026-09-18

## README

# @solidtv/nativescript

SolidTV on NativeScript: what an app needs to run
[`@solidtv/renderer`](https://github.com/solid-tv/renderer) and
[`@solidtv/solid`](https://github.com/solid-tv/solid) on Apple TV and iOS
without a browser. NativeScript is the runtime host, `@nativescript/canvas`
the WebGL surface, and `@nativescript/canvas-polyfill` the browser-shaped
environment the renderer expects. The plan, its phases and their outcomes are
in [docs/nativescript-integration-plan.md](docs/nativescript-integration-plan.md).

Status: phases 0 to 3 of the plan run on the iPhone and Apple TV simulators
(2026-09-17): the demo, the Siri Remote driven by real presses, and the
suspend and resume cycle. The same day all of it ran on a physical Apple TV
HD too: the exit test passed there, a looping animation survived a minute
in the background, and the measurement settled the WebGL decision below.
Only the App Store Connect export is open, for want of a paid team. Phase 4
(2026-09-17) runs the public
[demo app](https://github.com/solid-tv/solid-demo-app) on the Apple TV
simulator from its own repository, built from the same source as its web
build, with one Back rule added to that source and nothing else: the
shims and the platform below carry every seam. See
[docs/phase4-results.md](docs/phase4-results.md); the device run there
waits on a provisioning profile for the demo's bundle id.

## What is here

- `src/`, the package:
  - `TvPlatform`: the renderer `Platform` for this runtime. Sizes the canvas
    view's GL surface without touching its layout; keeps the web clock, which
    the runtime's `requestAnimationFrame` shares; hands the renderer the
    Canvas view `bindCanvas` registered and a target that swallows the
    append, so an app's own `createRenderer()` needs neither; and through the
    renderer's `resolveSettings` hook corrects the settings the host cannot
    honour: WebGL2, no image workers, both startup probes skipped, no
    inspector.
  - `bindCanvas(view)`: registers the Canvas view and makes `TvPlatform` the
    renderer's default platform, for an app written for the browser and
    booted here unchanged.
  - `rendererSettings(canvas, screen)` and `stubTarget`: the same as
    explicit renderer options, for an app that passes them itself as the
    harness does: 1920x1080 logical, SDF text, the platform's own settings
    (WebGL2, no image workers, both startup probes skipped, no inspector),
    and a target that keeps the renderer off `document`. WebGL2 because the
    plugin's WebGL1 context has no vertex array objects: 137 GL calls per
    frame against 32, on an Apple TV HD as on the simulators, at the same
    60 fps. Spread the result to override a setting, but not one of the
    platform's: `TvPlatform` applies those over whatever the settings say,
    so changing one takes a subclass that overrides `resolveSettings`,
    passed as the `platform`, which is how the harness runs its WebGL1
    comparison.
  - `KeyBridge`: turns native presses into `keydown` and `keyup` events for a
    focus manager, repeats a held key keyboard style, and is itself the event
    target: `useFocusManager(undefined, bridge)`. Given a `target`, it also
    dispatches every event there: `document`, for a focus manager that
    listens where a browser app's does.
  - `bindRemote(bridge, Application.ios.window, options?)`: the Siri Remote
    into the bridge on tvOS. A recognizer per button on the root view
    controller's view and four swipe recognizers. A press a `keydown` listener handled with `preventDefault()` is the app's; an unhandled Menu goes to the system and leaves the app. Swipes on the touch surface are not supported: they need not work, the swipe recognizers are a courtesy left unverified, and nothing depends on them.
  - `bindLifecycle(renderer, bridge, Application)`: on `suspend`, releases
    the bridge's held keys and pauses the renderer at once; on `resume`,
    resumes it. The renderer may be a function returning it, for an app
    that creates its renderer after the host has bound the lifecycle.
  - `mirrorConsole(file, options?)`: every console line into a file in the
    app's container, written once a second and on suspend and exit, for a
    device whose console the CLI cannot stream.
  - `@solidtv/nativescript/shims`: the browser globals the polyfill lacks or
    installs unusably, so an app written for the browser boots unchanged.
    `location` as a `file:` URL into the app folder, which is what makes a
    relative asset URL a file in it; a `history` with a real stack that
    raises `hashchange`, for a `HashRouter` and `history.back()`;
    `window.dispatchEvent`; a `document` whose listeners work; fixes to
    `XMLHttpRequest` for a file-backed `json` or `text` response, the
    runtime's `file:///app/` root, a JSON response asked for as a blob (what
    `fetch` asks for) and a request with no response type; and
    `console.table`. Import it right after the polyfill. The polyfill bugs
    behind each are listed in [docs/phase4-results.md](docs/phase4-results.md).
- `webpack.cjs`, `@solidtv/nativescript/webpack`: `chainSolidTV`, the Solid
  JSX rule, browser export conditions, `.js` imports resolved to `.ts`
  sources, the polyfill's optional-module ignore, and on request the
  hex-colour transform of `@lightningtv/vite-hex-transform` as a loader
  (`hexColorLoader.cjs`), aliases and `import.meta.env` members, for a
  `@nativescript/webpack` config.
- `harness/`: the app that runs the plan's phases on both simulators, with
  the recorded results. See [harness/README.md](harness/README.md).
- `docs/`: the plan, the phase 2, 3 and 4 briefs (historical), the
  [device brief](docs/nativescript-phase3-device-brief.md) for the device
  half of phase 3, and [phase4-results.md](docs/phase4-results.md), the
  demo app on the Apple TV.

## Using it in an app

### An app written for the browser, unchanged

The demo app runs on the Apple TV from its own `src/`, the source of its
web build, with a NativeScript project beside it and nothing of the host in
it: see
[solid-demo-app/nativescript](https://github.com/solid-tv/solid-demo-app/tree/main/nativescript).
Its boot file, in short:

```ts
// app/app.ts
import "@nativescript/canvas-polyfill";
import "@solidtv/nativescript/shims";
import { Application } from "@nativescript/core";
import { Canvas } from "@nativescript/canvas";
import { Config, renderer } from "@solidtv/solid";
import {
  KeyBridge,
  bindCanvas,
  bindLifecycle,
  bindRemote,
} from "@solidtv/nativescript";

// A key press a handler consumed stays with the app; Menu with nothing to
// handle it goes to the system and leaves the app. SolidTV 1.7 or later.
Config.preventDefaultOnHandledKeys = true;

canvas.on("ready", () => {
  // The app's own createRenderer() draws into this view through TvPlatform.
  bindCanvas(canvas);
  // The remote, into the bridge, onto document, where the app's focus
  // manager listens.
  const bridge = new KeyBridge({ target: document });
  bindLifecycle(() => renderer, bridge, Application);
  bindRemote(bridge, Application.ios.window);
  // The app's own entry: it reads the URL, sets Config, creates the
  // renderer and renders its routes, as in the browser.
  import("../../src/index");
});
```

The app keeps its `HashRouter`, its focus manager on `document`, its
`fetch`, its relative asset URLs, its `import.meta.env.BASE_URL` and its
`loadFonts`. The host's webpack config adds `chainSolidTV` with the
hex-colour loader over the app's source and the app's aliases, copies the
app's public fonts and assets into the bundle, and turns the bundle's type
check off, as the web build has none.

### An app that passes the host its settings

```ts
// app.ts
import '@nativescript/canvas-polyfill';
import '@solidtv/nativescript/shims';
import { Application, Screen } from '@nativescript/core';
import { isTvOS } from '@nativescript/core/platform';
import { Canvas } from '@nativescript/canvas';
import { createRenderer, registerDefaultShaders } from '@solidtv/solid';
import { useFocusManager } from '@solidtv/solid/primitives';
import {
  KeyBridge,
  bindLifecycle,
  bindRemote,
  rendererSettings,
  stubTarget,
} from '@solidtv/nativescript';

canvas.on('ready', () => {
  const { renderer, render } = createRenderer(
    rendererSettings(canvas, Screen.mainScreen),
    stubTarget,
  );
  registerDefaultShaders(renderer.stage.shManager);
  const bridge = new KeyBridge();
  bindLifecycle(renderer, bridge, Application);
  if (isTvOS) {
    bindRemote(bridge, Application.ios.window);
  }
  // The renderer's own font loading: the shims make it work on file URLs.
  renderer.stage.loadFont('sdf', {
    fontFamily: 'Ubuntu',
    atlasUrl: 'file://' + appPath + '/assets/Ubuntu-Regular.msdf.png',
    atlasDataUrl: 'file://' + appPath + '/assets/Ubuntu-Regular.msdf.json',
  });
  render(() => <App bridge={bridge} />);
});
```

```js
// webpack.config.js
const webpack = require("@nativescript/webpack");
const { chainSolidTV } = require("@solidtv/nativescript/webpack");

module.exports = (env) => {
  webpack.init(env);
  webpack.chainWebpack(chainSolidTV);
  return webpack.resolveConfig();
};
```

The app's `tsconfig.json` needs `"jsx": "preserve"` and
`"jsxImportSource": "@solidtv/solid"`. The bridge has no input of its own:
on tvOS `bindRemote` feeds it from the Siri Remote; elsewhere the host calls
`bridge.down(key)` and `bridge.up(key)` (or `tap`) from whatever reports
presses, on-screen buttons in the harness on iOS. A Back handler that
navigates calls `e.preventDefault()`, or Menu leaves the app: SolidTV's
focus manager does not call it for a handler that returned true. The
harness is the worked example of all of this, including the pixel ratios,
the `file://` asset URLs and what to log.

Peers: `@solidtv/renderer` 1.9.4 or later, which adds `setDefaultPlatform`,
the `Platform.resolveSettings` hook, the platform's own element lookup for
the render target, and relative font URLs resolved like image URLs;
`TvPlatform` imports `setDefaultPlatform`, so the package does not load on
1.9.3. That was the first release with the `./platform` export and the
`Platform.sizeCanvas` hook
([solid-tv/renderer#199](https://github.com/solid-tv/renderer/pull/199))
and the immediate pause, `renderer.pause({ immediate: true })`, which the
lifecycle binding calls
([solid-tv/renderer#201](https://github.com/solid-tv/renderer/pull/201)).
`solid-js` for the JSX preset, and webpack through `@nativescript/webpack`.
With SolidTV, 1.6.4 or later, which adds `Config.preventDefaultOnHandledKeys`,
the Menu contract, and ignores a second `useFocusManager` argument that
cannot listen; 1.6.3 was the first release where `useFocusManager` takes the
event target ([solid-tv/solid#63](https://github.com/solid-tv/solid/pull/63)),
and took that second argument for the target.

## Developing

pnpm 10 and Node 18 or later. The renderer comes from npm. The harness is
its own pnpm root under `harness/` and links this package from `..`; see
its README for the rest.

```bash
pnpm install
```

```bash
pnpm build
```

```bash
pnpm test
```

```bash
pnpm lint
```

### Releasing

`release-it`, the way the renderer and SolidTV release: on `main`, clean and
up to date, with tests and lint green, it asks for the increment, bumps
`package.json`, commits `Release x.y.z`, tags `x.y.z`, pushes, publishes to
npm (`prepack` builds first) and creates the GitHub release. It needs an npm
login with publish rights on `@solidtv` and a GitHub token in
`GITHUB_TOKEN`; the `gh` CLI's token serves:

```bash
GITHUB_TOKEN=$(gh auth token) pnpm release
```

Add `--dry-run` to see the plan without changing anything.

## License

Apache-2.0, see [LICENSE](LICENSE).

---
_Source: https://npm.io/package/@solidtv/nativescript · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
