作者尝试用 TypeScript 创建原生应用,过程中遇到诸多问题,总结如下:
- 为何选择 TypeScript:作者在开发命令行工具
lnstat
时,考虑到开发乐趣选择语言,已知语言中 Zig、TypeScript、JavaScript 较喜欢前三者,因项目涉及大量字符串处理,摒弃 Zig,决定用 JS 写前端代码、TS 写后端代码,且可通过 Bun 直接在后端运行 TS 无需先编译。 - 分发问题:使用 TypeScript 分发项目时存在问题,想让程序在命令行通过
lnstat
运行,可创建原生应用用 Bun 启动脚本,但考虑到流行度应改用 Node,而 Node 暂不支持直接运行 TS,分发脚本时需用 esbuild 转译,仍需用户安装 Node,跨平台支持多种系统和架构复杂,也可将 JS 运行时打包成独立二进制文件,如用pkg
或nexe
,但会导致二进制文件较大,或用较小的 JS 引擎如quickjs
,但需自定义文件系统 API 绑定且牺牲性能。 编译 TypeScript 的尝试:
- Surma 的尝试:Surma 尝试将 JS 转译为 C++来编译为原生代码,后因高效编译 JS 到原生代码几乎不可能而放弃。
- 作者的尝试:作者尝试将 JS 转译为 Zig 而非 C++,虽成功将简单 JS 程序转译并运行,但过程艰难,意识到此项目超出自己能力,放弃了
js2zig
项目。 - Porffor:下一个尝试编译 JS 的项目
porffor
完全用 JS 编写编译器,似乎更倾向于将 JS 编译为 WebAssembly 而非原生代码,且作者未找到关于如何将外部函数(文件系统 API)绑定到Porffor
编译代码的信息。 - Static Hermes:最后一个编译 JS 到原生代码的项目
static hermes (shermes)
用 C++编写,编译速度快,支持编译 TypeScript 和 JavaScript,能利用 TS 提供的类型信息生成更高效代码,作者选择用它尝试将应用编译为独立可执行文件。
- 编译 shermes:Static Hermes 未准备好生产,需从源码编译,作者在 Github 讨论中学习使用方法,因 6GB RAM 笔记本内存不足导致编译过程崩溃,需在有 16GB RAM 的 WSL 中构建项目并将构建的项目复制到笔记本,且需在 WSL 中与笔记本中相同位置构建项目,构建完成后将其添加到 PATH 中。
- 使用 shermes 运行程序:使用
shermes -typed -exec./main.ts
运行程序,-typed
标志告诉 shermes 编译 TypeScript,-exec
标志立即运行生成的原生代码,若要创建原生可执行文件,将-exec
替换为-o a.out
,测试表明 shermes 运行相同 JS 程序比quickjs
平均快 3.4 倍,运行 TypeScript 程序应会有更好性能。 - 导入外部依赖:由于 shermes 只是引擎而非运行时,需编写自己的
node:fs
实现,作者以创建my-project
为例说明如何包含外部依赖,包括设置文件结构、编写 Zig 代码和头文件、编译生成对象文件、设置环境变量、使用 shermes 编译脚本等,过程中遇到各种问题,如 Zig 编译器头文件生成器故障、字符串编码问题、不支持某些 ES6 特性等,通过偷代码并修改解决部分问题。 - 消除烦人的 TS 错误:VS Code 的 TS 服务器因不知道 shermes 全局变量而报告错误,通过创建
global.d.ts
文件解决。 - 将 lnstat 移植到 hermes:将 JS 传递到 Zig 中的字符串处理过程中遇到诸多问题,如字符串编码、不支持
TextEncoder
和TextDecoder
API、不支持某些 ES6 特性等,经过多次修改和调整,最终完成文件系统的 polyfill 和lnstat
的移植,但生成的可执行文件运行时立即段错误,经调试发现是 shermes 内部的共享对象问题,由于不是 shermes 维护者且无 C++经验无法解决。 - 事后分析:作者对未能成功用 shermes 编译
lnstat
为原生可执行文件感到失望,认为 shermes 是年轻项目未准备好生产,可能自己的方法有误,考虑学习类似 JavaScript 但更严格的 Dart 语言并重新编写lnstat
。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。