Core concepts
TSX apps
Write an app in TSX with Tailwind classes, compiled ahead with no Node.
A TSX app
A TSX app is a Go package that embeds its sources and runs its entry:
//go:embed *.tsx nsgo.yaml
var src embed.FS
var Root = compiler.App(src, "App.tsx")
func main() { nsgo.Run(Root) }
import { mount, signal, Text, View } from "nsgo";
function App() {
const count = signal(0);
return (
<View class="gap-3 p-6" onPress={() => count.update((n) => n + 1)}>
<Text class="text-lg font-bold">Pressed {count()} times</Text>
</View>
);
}
mount(App);
go tool nsgo init -template tsx writes one, and examples/tsx is another. nsgo dev and nsgo build compile the TSX ahead of time, so packages ship no compiler; go run and go test compile it as the app starts. No Node is needed either way.
Components and modules
TSX imports the primitives from nsgo, base’s components from nsgo/base and ui’s from nsgo/ui, once the app imports tsx/ui; examples/gallery-tsx shows them.
Go from TSX
Register Go for TSX to import:
tsx.Register("app/native", map[string]core.Node{"Badge": Badge{}})
tsx.RegisterFunctions("app/native", map[string]any{
"greet": func(name string) string { return "Hello, " + name },
})
import { Badge, greet } from "app/native" then takes Badge’s fields as props and calls greet. Functions taking a context.Context become promises, and tsx.RegisterClass makes Go types classes TSX creates with new.
Signed packages
An app can run TSX it downloads, such as its own screens delivered anew or plugins, once it verifies the package. Declare what the package’s code may reach in its nsgo.yaml; it is refused everything else:
capabilities:
network:
hosts: [api.example.com, "*.cdn.example.com"]
open_url: true # or { schemes: [https] }
host: [search, "player.*"] # the app's handlers it may call
# webview: { hosts: [...], input: true, media: [microphone, camera] }, clipboard, picker, speech, status_bar
Keep a root key offline and certify release keys with it, then sign the app in that directory. go tool nsgo keygen prints a key pair and writes no file; run it once for the root and once for each release key. Public keys go in flags and the app’s code; secret keys stay out of the repository and reach the tool only through NSGO_ROOT_KEY and NSGO_RELEASE_KEY:
go tool nsgo keygen # once for the root, once for the release key
NSGO_ROOT_KEY=<root secret> go tool nsgo certify -key <release public> -apps 'com.example.*'
NSGO_RELEASE_KEY=<release secret> go tool nsgo publish -cert release.cert plugin
go tool nsgo verify -root <root public> plugin/dist/Plugin-1.0.0.nsgopkg
The app trusts the root’s public key, written in its code, verifies the package off the engine’s goroutine and shows it in tsx.View, sandboxed beside its own views. The package reaches the app only through the host channel the view gives it, and keeps its own part of the app’s storage:
trust, err := pkg.OpenTrust(filepath.Join(cx.DataDir(), "trust"))
trust.AddRoot(root)
p, err := trust.Verify(data) // refuses tampered, revoked and older builds
channel := core.NewHost()
channel.Handle("search", func(c *core.HostCall) { c.Resolve(results(c.Data)) })
view := tsx.View{Package: p, Sandbox: tsx.Sandbox{Host: channel}}
core.Catch{Try: view.Render, Fallback: failed}
trust.Accept(p) // once it ran, older builds are refused
Sandbox.Grant gives it less than it declares, Sandbox.Limits other time budgets and quotas, and Sandbox.Modules Go functions and components beyond NSGo’s own. Its keyboard shortcuts, back handlers and navigation stacks take effect only while the keyboard focus is in its view, and it moves the focus itself (focus(), autoFocus, Keyboard.dismiss, setAccessibilityFocus) only while the focus is already there or in answer to the user’s press in it. It shows in a view of its own that fills the room its parent gives it, no more, and clips it, menus, popovers and toasts included; in a scroll view, put it in a view to let it take its content’s height. A dialog, an alert, a context menu or an announcement of its shows only in the same way, and back closes a dialog of its even if it does not remove it. A package whose entry exports functions is a plugin of functions: tsx.Load(cx, p, tsx.Sandbox{}) runs it unseen and Plugin.Call calls them. go tool nsgo dev -limits runs an app as such a view would, and go tool nsgo revoke signs a list of revoked release keys and packages for Trust.UpdateRevocations. examples/plugins asks the user to grant a package’s capabilities, installs it and runs it.
The web cannot isolate a package from the page yet: its code would reach the page’s own network, clipboard and storage, the trust store’s included, past its capabilities. There tsx.View and tsx.Load refuse a package with tsx.ErrUnisolated, unless you set Sandbox.WebUnisolated to run it with the page’s authority, which suits only packages you would trust with the app’s own.
Editors and tests
A tsconfig.json extending .nsgo/tsconfig.json gives editors the TSX’s types, which go tool nsgo dev and go tool nsgo check write. *.test.tsx files beside the app, written with nsgo/test, run with its Go tests (Testing).