文档

打包

nsh package 把应用变成可以安装的产物:用于 iOS 模拟器的 .app、用于 iOS 真机的已签名 .ipa、用于 Android 的已签名 .apk、用于 macOS 的 ad hoc 签名 .app、用于 Linux 的目录和 .tar.gz、用于 Windows 的目录和 .zip,或用于 Web 的静态站点;--target gpui 生成本机的桌面包,只是换成 GPUI 运行器。它把应用的 release 构建与 NSH 自带的预构建运行时组合在一起,因此不需要 Rust 工具链,不需要 Xcode 项目,也不需要 Gradle。

nsh package App.tsx --target ios-simulator --run     # build, install and launch in the simulator
nsh package App.tsx --target android --run           # build, install and launch on a device or emulator
nsh package App.tsx --target macos --run             # build and launch on this Mac
nsh package App.tsx --target linux --run             # build and launch on this machine
nsh package App.tsx --target windows --run           # build and launch on this machine
nsh package App.tsx --target web --run               # build, serve on this machine and open in the browser
nsh package App.tsx --target ios --sign "Apple Development: Jane Doe (ABCDE12345)" --profile Dev.mobileprovision

应用以 release 模式构建(压缩并编译为 QuickJS 字节码),与 nsh build --release 的构建结果完全相同。除非传入 --out,否则输出到 out/。

#应用元数据:nsh.json

nsh package 读取入口文件同目录下的 nsh.json。nsh init 会生成一个:

{
  "name": "Gallery",
  "id": "com.example.gallery",
  "version": "1.0.0",
  "build": 1,
  "icon": "icon.png"
}
键类型默认值用途
namestring入口文件所在目录的名称图标下方显示的名称(CFBundleDisplayName、Android 的 android:label),也用作输出文件名。
idstringcom.example.<name>Bundle identifier / Android 包名。
versionstring"1.0.0"用户可见的版本号(CFBundleShortVersionString、versionName)。
buildnumber1构建号(CFBundleVersion、versionCode)。每次上传新版本都要递增。
iconstring无一张正方形 PNG,最好是 1024×1024,路径相对于 nsh.json。

nsh.json 还可以包含 tailwind(见 Tailwind)和 capabilities(见沙箱与能力);它们由打包器读取。未知的键会导致报错。

每个键都可以在命令行上覆盖,这在 CI 中很方便:

nsh package App.tsx --target android --app-version 1.4.0 --build 42

#选项

参数说明
--target <ios | ios-simulator | android | macos | linux | windows | gpui | web>构建目标(必填)。
-o, --out <dir>输出目录。默认为 out。
--name <name>显示名称。
--id <id>Bundle identifier,如 com.example.app。
--app-version <version>版本号,如 1.2.0。
--build <n>构建号。
--icon <png>应用图标:一张正方形 PNG,最好是 1024×1024。
--sign <identity>iOS:用于真机的代码签名身份。
--profile <file>iOS:用于真机的描述文件(.mobileprovision)。
--keystore <file>Android:release keystore(默认使用 debug 密钥)。
--key-alias <alias>Android:keystore 中的密钥别名。
--keystore-password <password>Android:keystore 密码;也可从 NSH_KEYSTORE_PASSWORD 读取。
--run构建完成后安装并启动:在 iOS 模拟器中,或在 adb 能看到的 Android 设备或模拟器上;macOS、Linux、Windows 和 GPUI 应用在本机启动;Web 站点在本机提供服务并在浏览器中打开。不适用于 --target ios。

#iOS 模拟器

nsh package App.tsx --target ios-simulator --run

写出 out/<Name>.app,使用 ad hoc 签名(模拟器需要签名,但不需要签名身份)。加上 --run 时,如果没有正在运行的模拟器,它会先启动一个,再安装并启动应用。需要安装 Xcode。

#iOS 真机

nsh package App.tsx --target ios \
  --sign "Apple Distribution: Example Inc (ABCDE12345)" \
  --profile AppStore.mobileprovision

写出 out/<Name>.ipa,可用于 TestFlight、App Store 上传工具或直接安装(取决于描述文件)。具体过程如下:

  • 描述文件以 embedded.mobileprovision 的名称复制进应用。
  • entitlements 取自描述文件。NSH 会检查描述文件的 application identifier 是否覆盖应用的 id(TEAMID.* 或 TEAMID.com.example.* 这样的通配描述文件也可以),不覆盖则报错退出。
  • 用 codesign 以指定身份签名应用,再压缩成 .ipa(Payload/<Name>.app)。

查看可用的签名身份:

security find-identity -v -p codesigning

如果没有传 --sign 和 --profile,NSH 会写出一个未签名的 out/<Name>.app 并给出提示;它无法直接安装到真机上。

应用支持 iPhone 和 iPad,支持竖屏和横屏(倒置竖屏仅限 iPad),要求 iOS 15.0 或更高版本。

#Android

nsh package App.tsx --target android --run

写出 out/<Name>.apk。APK 直接用 SDK 的构建工具(aapt2、zipalign、apksigner)组装,不经过 Gradle。要求:

  • Android SDK,通过 ANDROID_HOME(或 ANDROID_SDK_ROOT,或 ~/Library/Android/sdk)查找,需要安装 build tools 和 platforms;android-35 包。缺少时 NSH 会告诉你对应的 sdkmanager 命令。
  • 一个 JDK,用于首次创建 debug keystore。

应用的目标 API 为 35,要求 API 28(Android 9)或更高版本;包含适用于 arm64-v8a 和 x86_64 的原生代码。

#发布签名

不传 --keystore 时,APK 使用标准的 debug 密钥(~/.android/debug.keystore,不存在时自动创建)签名,NSH 会警告它不能用于应用商店发布。正式发布时,先创建一次 keystore,然后传入:

keytool -genkeypair -v -keystore release.jks -alias upload -keyalg RSA -keysize 2048 -validity 10000

NSH_KEYSTORE_PASSWORD=… nsh package App.tsx --target android \
  --keystore release.jks --key-alias upload

建议使用环境变量而不是 --keystore-password,以免密码留在 shell 历史记录中。

#macOS

nsh package App.tsx --target macos --run

写出 out/<Name>.app:可执行文件位于 Contents/MacOS/,nsh/bundle.nshc、nsh/assets/… 和 nsh/app.json(窗口标题和应用的外观) 位于 Contents/Resources/。应用使用 ad hoc 签名,要求 macOS 14 及以上;要分发它,请用 Developer ID 签名并进行公证。见 macOS。

#Linux

nsh package App.tsx --target linux --run

写出 out/<Name>/(可执行文件 <Name>、nsh/bundle.nshc、nsh/assets/…、包含窗口标题和应用 id 的 nsh/app.json、<id>.desktop 和图标 <id>.png),以及同样内容的 out/<Name>.tar.gz。请在 Linux 上打包:运行器是为构建它的机器构建的。运行应用的机器上必须装有 GTK 4(4.14 及以上)、Pango 和 WebKitGTK 6.0(用于 Web 视图)。如何安装应用及其桌面条目:见 Linux。

#Windows

nsh package App.tsx --target windows --run

写出 out\<Name>\(带应用图标的可执行文件 <Name>.exe、nsh\bundle.nshc、nsh\assets\…,以及包含窗口标题、应用 id、版本和外观的 nsh\app.json),以及同样内容的 out\<Name>.zip。不需要任何工具。请在 Windows 上打包:运行器是为构建它的机器构建的,而且只有 Windows 能嵌入图标。应用可以从目录被复制到的任何位置运行,不需要安装。见 Windows。

#GPUI

nsh package App.tsx --target gpui --run

写出的内容与本机上 --target macos、--target windows 或 --target linux 相同,只是可执行文件换成 GPUI 的发布版运行器 (nsh-gpui-app),而不是原生后端的。请在应用面向的操作系统上打包:运行器是为构建它的机器构建的。见 GPUI。

Linux 上的 GPUI 包动态链接 WebKitGTK 4.1(Web 视图)和 GStreamer(视频),因此运行它的机器需要安装它们,以及 GTK 3 和 GStreamer 的 base、good 插件(Debian / Ubuntu:libwebkit2gtk-4.1-0、libgstreamer1.0-0、libgstreamer-plugins-base1.0-0、gstreamer1.0-plugins-good),此外还需要 Vulkan 驱动和 libxkbcommon。

#Web

nsh package App.tsx --target web --run

写出 out/<Name>/,一个静态站点:index.html、manifest.webmanifest,以及运行时(nsh.<digest>.js、nsh.<digest>.wasm)、应用 (app.<digest>.js,即其 JS 源码)、资源和图标,每个文件都以其内容命名,托管方可以永久缓存它们。把目录部署到任意 Web 服务器或 CDN 即可;--run 在 http://127.0.0.1:4173/ 上提供服务并打开浏览器。缓存头和嵌入方式见 Web。

#图标

在 nsh.json 中("icon": "icon.png")或通过 --icon 提供一张正方形 PNG,最好是 1024×1024:

  • iOS: 由 Xcode 的 actool 编译进应用的资源目录(Assets.car),iOS 所需的各种尺寸都由它生成。
  • Android: 缩放到 192×192,用作启动器图标(mipmap-xxxhdpi)。
  • macOS: 由 sips 转换为 AppIcon.icns。
  • Linux: 复制为 <id>.png,.desktop 文件以它作为 Icon。
  • Windows: 缩放为 16 到 64 像素以及 256 像素,作为图标嵌入可执行文件,窗口、资源管理器和任务栏都会显示它。
  • Web: 缩放为 32 像素(favicon)、180 像素(apple-touch-icon)以及 192 和 512 像素(Web 应用清单)。

没有图标时,系统显示默认图标。

#包里有什么

包中包含应用的字节码、capabilities.json(当应用声明了能力时)以及应用导入的资源,与预构建运行时放在一起:

目标运行时你的应用
iOSnsh-ios-app 可执行文件<Name>.app/nsh/bundle.nshc、nsh/assets/…
Androidclasses.dex、lib/<abi>/libnsh.so(不压缩存储,16 KB 对齐)assets/nsh/bundle.nshc、assets/nsh/assets/…
macOSnsh-macos-app 可执行文件,重命名为 <Name><Name>.app/Contents/Resources/nsh/bundle.nshc、nsh/assets/…
Linuxnsh-linux-app 可执行文件,重命名为 <Name><Name>/nsh/bundle.nshc、nsh/assets/…
Windowsnsh-windows-app.exe 可执行文件,重命名为 <Name>.exe<Name>\nsh\bundle.nshc、nsh\assets\…
GPUInsh-gpui-app 可执行文件,按本机操作系统的方式重命名与本机操作系统相同
Webnsh.<digest>.wasm、nsh.<digest>.js<Name>/app.<digest>.js(JS 源码)、assets/…、index.html

打包出的应用不注册任何原生组件;如需使用原生组件,请把 NSH 嵌入你自己的应用。

#查找运行时

nsh package 会在 CLI 旁边查找预构建运行时(发行包中的 bin/../runtime)。在 NSH 的源码仓库中,它使用 target/<triple>/<profile> 和 target/android/<profile>,profile 与 CLI 自身一致(debug 版 CLI 使用 debug 版运行时):用 cargo build -p nsh-ios-app --target aarch64-apple-ios-sim(真机用 aarch64-apple-ios)和 cargo xtask android 构建它们(release 版 CLI 需加上 --release);nsh-macos-app(在 macOS 上)、nsh-linux-app(在 Linux 上)、nsh-windows-app(在 Windows 上)和 nsh-gpui-app(在三者任一上)为本机构建,位于 target/<profile>(cargo build -p nsh-macos-app、cargo build -p nsh-linux-app、cargo build -p nsh-windows-app、cargo build --manifest-path crates/gpui/Cargo.toml --target-dir target -p nsh-gpui-app);Web 运行时不论 CLI 的 profile 如何,都以 web profile 构建,位于 target/wasm32-unknown-unknown/web/。环境变量 NSH_RUNTIME 可以让 CLI 改用另一个目录,其结构与发行包的 runtime/ 相同(ios/iphoneos、ios/iphonesimulator、android/release、macos、linux、windows、gpui、web 等)。

#另请参阅