Esta página foi traduzida automaticamente e pode conter erros. Ler o original em inglês

Documentação

Estrutura do projeto

Um app NSH é um diretório com um nsh.json e seu código: um crate Rust cujo componente raiz é nomeado por nsh.json, ou um módulo de entrada TSX, ou ambos. Todo o resto (mais módulos, imagens, fontes, arquivos JSON) é encontrado a partir dali: os módulos de um crate e seu diretório assets/, ou os arquivos que a entrada TSX importa, de modo que não há lista de arquivos para manter.

Um app escrito em Rust é um projeto Cargo:

my-app/
├── nsh.json           name, id, version, icon, the crate ("rust") and its root component ("root"), theme
├── Cargo.toml         the crate: nsh-core, nsh-base and your dependencies
├── src/
│   ├── lib.rs         the root component and the crate's UI set (#[native_module])
│   ├── main.rs        cargo run: the root on this machine's desktop backend
│   └── screens/       your own modules (any layout you like)
├── assets/            files the crate ships: asset!("logo@2x.png"), fonts
├── .gitignore         .nsh/ and target/
├── .nsh/              generated: the runners' sources, test entries, build output (.nsh/out)
└── target/            Cargo's build directory

Um app TSX é uma entrada e os módulos que ela importa:

my-app/
├── App.tsx            the entry module: calls mount()
├── App.test.tsx       its tests (nsh test)
├── nsh.json           name, id, version, icon, Tailwind theme, capabilities
├── tsconfig.json      TypeScript settings for editors
├── .gitignore         .nsh/ and target/
├── components/        your own modules (any layout you like)
│   └── Header.tsx
├── assets/
│   ├── logo@2x.png    imported images
│   └── Inter.ttf      imported fonts
└── .nsh/              generated: type declarations, development host builds, test entries, build output (.nsh/out)

Um app com ambos lista seus crates em "rust" e tem uma entrada TSX que os importa (Desenvolvendo com Rust).

Todo comando recebe o diretório do app ou sua entrada como argumento (nsh run my-app, nsh run my-app/App.tsx). O diretório de nsh.json e da entrada é a raiz do app: nsh.json é lido dali, os caminhos de assets são relativos a ela, e .nsh/ é gravado nela. Em um app Rust (nsh.json "root"), o diretório executa o componente raiz; informar uma entrada TSX executa essa entrada.

#O componente raiz

A raiz de um app Rust é um componente de seu crate, que o "root" de nsh.json nomeia por seu caminho no crate (App, shell::Gallery). Uma entrada TSX é um módulo normal que termina renderizando o componente raiz:

// src/lib.rs; nsh.json: { "rust": ".", "root": "App" }
mod screens;

use nsh_core::prelude::*;

/// What a TSX entry may import from the crate.
#[native_module("my-app/native")]
fn native(registry: &mut Registry) {
    registry.register::<App>().assets(assets!());
}

#[component]
pub fn App() -> impl IntoElement {
    screens::home()
}

nsh run e nsh package montam uma raiz Rust em todas as plataformas, sem QuickJS. Em TSX, mount(component, props?) renderiza o componente na superfície do app e retorna uma função que o remove novamente; um app a chama uma vez. O código no nível superior da entrada é executado quando o bundle é carregado, antes do primeiro frame.

#nsh.json

nsh.json fica na raiz do app. nsh init grava name, id, version e build e, para um app Rust, o crate e seu componente raiz:

{
  "name": "Gallery",
  "id": "com.example.gallery",
  "version": "1.0.0",
  "build": 1,
  "rust": ".",
  "root": "App",
  "theme": "theme.json"
}

Todas as outras chaves são opcionais; um app TSX também pode omitir as quatro. O nsh.json de um app TSX poderia ser assim:

{
  "name": "Gallery",
  "id": "com.example.gallery",
  "version": "1.0.0",
  "build": 1,
  "icon": "icon.png",
  "tailwind": {
    "colors": { "brand": { "500": "#7c3aed", "600": "#6d28d9" } },
    "radius": { "card": 14 }
  },
  "capabilities": {
    "network": { "hosts": ["api.example.com", "*.cdn.example.com"] }
  }
}
Chave Tipo Usada por Descrição
name string package O nome de exibição sob o ícone do app. Padrão: o nome do diretório da entrada.
id string package, publish, Storage Identificador de bundle / application id em formato DNS reverso, p. ex. com.yourcompany.app; exigido por package e publish. nsh run usa com.example.<name> sem ele.
version string package, publish A versão visível ao usuário, p. ex. 1.2.0 (CFBundleShortVersionString, Android versionName).
build number package, publish O número do build (CFBundleVersion, Android versionCode). Pacotes assinados exigem que ele aumente a cada release.
minRuntime string publish O runtime NSH mais antigo que executa o pacote assinado, p. ex. 0.2.0. Padrão: a versão da CLI (Pacotes assinados).
icon string package Um PNG quadrado, idealmente de 1024×1024, relativo a nsh.json.
tailwind object todo build Estende o tema do Tailwind com o qual suas strings class são resolvidas (Tailwind).
appearance string todo build, package "light", "dark" ou "system" (o padrão): um app que é sempre claro ou sempre escuro declara isso aqui. As classes colorScheme() e dark:, o tema dos componentes e os controles da plataforma o seguem desde o início (Dispositivo e ambiente).
theme string or object todo build Os design tokens do app (os cssVars do shadcn/ui, sobre o tema neutral padrão), com os quais as classes de token, token() e os componentes Rust desenham: o caminho de um arquivo de tema relativo a nsh.json, ou o tema inline (Desenvolvendo com Rust, Tokens de tema).
statusBar object todo build, package O padrão da barra de status sob toda página e diálogo (iOS e Android): { "foreground": "auto" | "light" | "dark", "hidden": boolean }, ambos opcionais. Apps iOS empacotados a exibem desde a tela de lançamento (Barra de status).
capabilities object todo build O que o app pode acessar fora de sua VM: hosts de rede, hosts de web view, a barra de status (Sandbox e capabilities).
resolve object todo build Como os imports simples são resolvidos: as export conditions dos pacotes npm e os aliases (Pacotes npm).
rust string or string[] todo comando O crate Rust do app, ou uma lista deles, relativo a nsh.json: seus componentes e módulos, que o TSX importa pelo especificador #[native_module] de cada crate (Desenvolvendo com Rust).
web object package O que um pacote web adiciona à sua página. web.host: um módulo JS relativo a nsh.json que exporta createHost(player), o qual responde às chamadas de host do app; web.connect: os hosts que as próprias requisições desse módulo alcançam, como padrões de capabilities.network.hosts, que a Content-Security-Policy da página permite (Web).
root string run, package O componente raiz de um app Rust, seu caminho no crate rust, o primeiro de uma lista (App, shell::Gallery): nsh run e nsh package no diretório do app o montam sem QuickJS, em todas as plataformas; nsh run App.tsx ainda executa a entrada TSX (Desenvolvendo com Rust).

As flags de nsh package e nsh publish (--name, --id, --app-version, --build, --icon, --min-runtime) sobrescrevem os valores do arquivo (CLI).

tailwind, theme, appearance, statusBar, capabilities e resolve são verificados quando o app é compilado. Um erro de digitação é um erro de build, e não uma configuração que silenciosamente não faz nada:

error building App.tsx failed
nsh.json: tailwind: unknown theme key `colours`

#capabilities

Sem a chave capabilities, um app pode usar livremente a rede, as web views e a barra de status: um app standalone pertence ao próprio desenvolvedor. Com a chave, tudo o que não está listado é negado, tanto em desenvolvimento quanto em apps empacotados:

"capabilities": {
  "network": {
    "hosts": ["api.example.com", "*.cdn.example.com"],
    "http": [{ "host": "quotes.example.com", "methods": ["GET"], "pathPrefix": "/v1/" }]
  },
  "webview": { "hosts": ["*.example.com"] },
  "statusBar": true
}

network cobre fetch, imagens remotas e fontes de vídeo; assets locais são sempre permitidos. Um app que incorpora o NSH pode conceder menos do que um app declara. Os detalhes estão em Sandbox e capabilities.

#tsconfig.json e .nsh/types

O NSH não precisa de node_modules. Em vez disso, todo comando que compila o app (init, build, test, dev, run, package, publish, check) grava as declarações TypeScript de tudo o que o NSH fornece em .nsh/types, e as configurações TypeScript do NSH em .nsh/tsconfig.json. O tsconfig.json do app as estende:

{
  "extends": "./.nsh/tsconfig.json",
  "include": ["**/*.ts", "**/*.tsx"]
}

.nsh/tsconfig.json aponta os imports de nsh e os crates do seu app para .nsh/types, inclui as declarações de globais, assets e tokens e define as opções de compilador que os apps NSH usam (lib, jsx, strict, …). O NSH o mantém atualizado: quando uma nova versão adiciona uma biblioteca ou um caminho, seu app o recebe no próximo build, e seu tsconfig.json permanece como você o escreveu. As opções que você adiciona a tsconfig.json prevalecem sobre as do NSH.

Arquivo em .nsh/types/nsh/ Declara
index.d.ts nsh: signal, computed, effect, mount, View, Text, … e reexporta os tipos abaixo
types.d.ts Estilos (ViewStyle, TextStyle, …), as props e os eventos de cada elemento, as refs (ViewRef, InputRef, …: o que um ref recebe)
jsx-runtime.d.ts O JSX runtime que o TypeScript resolve por meio de jsxImportSource (o namespace JSX)
globals.d.ts If e Each; console, timers, requestAnimationFrame, fetch e as demais APIs globais
assets.d.ts O que a importação de uma imagem ou fonte retorna
tokens.d.ts Tokens: os design tokens do app, a partir de seu "theme", que tipam token()
mobile.d.ts, desktop.d.ts Um arquivo por módulo (nsh/mobile, nsh/desktop)
base.d.ts nsh/base

lib é ES2023 sem DOM: não há document nem window. Os arquivos em .nsh são regenerados (somente quando seu conteúdo muda), portanto não os edite nem os faça commit; .gitignore já exclui .nsh/. Se o seu editor mostrar nsh como ausente após clonar um projeto, execute qualquer comando de build uma vez, p. ex. nsh check.

.nsh/tsconfig.json adiciona "ESNext.Disposable", que declara as declarações de using e Symbol.dispose, que o runtime possui: using account = new Account() libera o valor de uma classe Rust ao final do bloco. Seus jsxImportSource: "nsh" e jsx: "preserve" só importam para o editor: o NSH compila JSX com seu próprio compilador, independentemente do que o tsconfig.json diga. O TypeScript é verificado pelo seu editor ou por nsh check; os demais comandos removem os tipos sem verificá-los.

Um app criado por um nsh init anterior recebe o novo tsconfig.json no próximo build quando o seu é o template do NSH, sem alterações; o NSH imprime uma linha informando isso. Um tsconfig.json que você alterou é seu: nsh check indica quando ele não estende ./.nsh/tsconfig.json, e você adiciona a linha extends e remove as opções que ele define.

#Módulos

O código de um app Rust são os módulos de seu crate (mod screens;), e suas dependências vêm do crates.io como as de qualquer crate; os crates do próprio NSH são nsh-core (as primitivas, signals e estilos tipados, use nsh_core::prelude::*) e nsh-base (os componentes base e o tema).

Em TSX, divida o app em quantos módulos .ts e .tsx quiser e importe-os com caminhos relativos; a extensão pode ser omitida:

import { Header } from "./components/Header";
import { formatPrice } from "./lib/format";
import strings from "./strings.json";

Todos são reunidos em um único script. Arquivos JSON são importados como seu valor já interpretado.

#Carregando código sob demanda

import() de um dos módulos do app retorna uma promise de seus exports, como em um navegador:

const open = async () => {
  const { Settings } = await import("./screens/Settings");
  screen.set(() => Settings);
};

Apps em dispositivos distribuem um único bundle, portanto o módulo já está lá e a promise é resolvida imediatamente. Um app web publicado dividido (nsh build --split) recebe o módulo, e o que só ele usa, como um arquivo separado que é baixado na primeira vez que import() o solicita (Web). O especificador deve ser um caminho relativo literal: o bundler precisa ver qual módulo é o desejado.

#Módulos do NSH

Os imports de nsh e nsh/... não são incluídos no bundle: o runtime os fornece nativamente.

Módulo O que ele tem
nsh Reatividade (signal, computed, effect, …), mount, split, context; as primitivas View, Text, Scroll, Image, List, Canvas, WebView, Video (o selectable de View para seleção de texto); insets de área segura (safeArea, safeAreaInsets) e o teclado virtual (Keyboard, keyboardFrame); animações nativas; PanResponder; o ambiente (platform, windowSize, colorScheme, fontScale, pixelRatio, locale); estado do leitor de tela e de redução de movimento, anúncios; a área de transferência, o seletor de fotos, o armazenamento, o canal com o host, atalhos de teclado; componentes e funções que o app anfitrião registra (referência)
nsh/base A base sem estilo da UI de um app: pilhas de navegação e suas páginas (NavStack, NavPage, createNavStack), diálogos (Dialog), um alvo de toque (Button), um campo de texto (Input) e o diálogo de alerta do sistema (AlertDialog); páginas e diálogos definem a barra de status (statusBar) (referência)
nsh/mobile O que o iOS e o Android têm: TabBar, InputAccessoryView (iOS), BackHandler (Android) (referência)
nsh/desktop O que macOS, Windows e Linux têm: Window, titleBar, Menu (referência)

A referência de módulos documenta cada um deles. Importar um módulo que o NSH não tem (nsh/foo) é um erro de build. O mesmo vale para um nome que seu código usa e que o módulo não exporta (import { Buton } from "nsh/base"): o erro aponta para o import e indica o export mais próximo (did you mean `Button`?). Nomes usados apenas como tipos são verificados por nsh check.

#Pacotes npm

O NSH não precisa de nada do npm, mas o bundler resolve imports simples a partir de um diretório node_modules se você instalar pacotes com um gerenciador de pacotes. Os pacotes resolvem seus builds para navegador, porque o runtime fornece APIs web (fetch, streams, URL, TextEncoder, …) e não as do Node (fs, Buffer, process). Pacotes que mexem no DOM não funcionam: não há DOM.

Um pacote cujo build para navegador usa o DOM costuma ter outro build para workers ou React Native. resolve.conditions escolhe as export conditions com as quais os pacotes são resolvidos, no lugar do ["browser"] padrão: import, require, module e default sempre se aplicam, e sem browser os pacotes são resolvidos pelos campos module e main. resolve.alias substitui um import: por um caminho relativo a nsh.json (./ ou ../), por outro pacote, ou por false para um módulo vazio. O TypeScript (nsh check, editores) tipa o import como escrito: um nome que não é um pacote instalado obtém seus tipos de um arquivo .d.ts do app.

"resolve": {
  "conditions": ["worker"],
  "alias": { "dom-only-helper": "./shims/helper.ts", "node-polyfill": false }
}
// shims/aliases.d.ts
declare module "node-polyfill";

solid-js não pode ser importado: o bundler o recusa e aponta para nsh, signal e computed e effect do próprio NSH, que o substituem.

#Assets

Imagens e fontes são distribuídas com o app. Um crate Rust lê cada arquivo de seu diretório assets/ em tempo de compilação com asset! e os distribui todos com registry.assets(assets!()) em sua função #[native_module]; o TSX os importa como módulos, e o bundler registra cada arquivo importado (e as variantes de densidade de pixel de uma imagem). nsh build os copia para .nsh/out/assets/, nsh package os coloca no app, e durante o desenvolvimento eles são servidos ao app.

use nsh_core::element::{FontAsset, ImageAsset, asset};

const LOGO: ImageAsset = asset!("logo@2x.png");      // its url, and its size: 64×64 points
const INTER: FontAsset = asset!("Inter.ttf");        // its uri, and its family: "Inter"

image(LOGO)
text("Hello").font_family(INTER.family)
Extensão O import retorna
.png, .jpg, .jpeg, .gif, .webp, .heic Um ImageAsset: { url, size, scales? }, dimensionado em pontos. Um sufixo @2x / @3x divide o tamanho em pixels por 2 / 3. Um arquivo sem sufixo também distribui seus irmãos @1.5x–@4x, listados em scales, e a variante da tela é carregada (densidades de pixel).
.svg Um ImageAsset dimensionado em pontos pelos width e height do SVG (ou viewBox).
.ttf, .otf, .woff, .woff2 O nome da família da fonte, uma string. A fonte é registrada antes do primeiro frame.
.json O JSON interpretado

Veja Imagens e assets e Fontes.

#Diretórios gerados

  • .nsh/: tudo o que o NSH gera: as declarações de tipos (.nsh/types), os códigos-fonte dos runners de um app Rust (.nsh/rust), o que nsh run instala em simuladores e dispositivos (o app host do Catalyst, o APK de desenvolvimento do Android) e .nsh/out, a saída padrão de nsh build (bundle.js, bundle.js.map, bundle.nshc, capabilities.json, assets/; para um app Rust, app.json e assets/) e de nsh package (.app, .ipa, .apk, um site); -o grava em outro local. Pode ser excluído com segurança; ele é recriado.
  • target/: o diretório de build do Cargo para os crates e runners de um app (ou CARGO_TARGET_DIR).