Hyperimport - 在 TypeScript 中导入 c、rust、zig 等文件

2025-06-10

Hyperimport - 在 TypeScript 中导入 c、rust、zig 等文件

“什么?我读的标题对吗?”

是的!我们一开始就看一个例子,何乐而不为呢?

你将能够做到这一点,

索引.ts

import { add } from "./add.rs";
console.log(add(5, 5)); // 10
Enter fullscreen mode Exit fullscreen mode

地址

#[no_mangle]
pub extern "C" fn add(a: isize, b: isize) -> isize {
    a + b
}
Enter fullscreen mode Exit fullscreen mode

...甚至更多,比如在 TypeScript 中从 libc 导入原生 C 函数。查看指南

“等等!什么??这怎么可能!?”

很久以前,我开发webview-bun项目的时候,它本质上是 Bun 的 webview 库 API 的 FFI 包装器。我突然想到,为什么我不能直接将 webview 的 C 源文件导入到 TypeScript 中呢?如果通过 FFI API,我们可以从本质上是从源文件编译而来的共享库(比如 c、rust、zig 等)中导入函数,那么我何不创建一种方法,将 TypeScript 中的函数导入到源文件中,并自动化执行其中的步骤?这样一来,最终用户看起来就像直接从源文件导入一样,而所有复杂的部分都在内部自动管理。

我在 zig 中编写了一个名为 calc 的简单函数,用于将两个数字相加。在 TypeScript 中,我编写了一个导入函数,该函数会获取该 zig 文件的路径,启动一个子进程来调用 zig 编译器,将该文件编译成共享库文件,然后使用 FFI API 打开该共享库,并返回包含 calc 函数的符号。因此,当我使用该函数导入 zig 文件时,这些内部步骤在后台执行,calc 函数也能正常工作。之后,当我将 zig 函数中的运算从加法改为减法并执行 TypeScript 文件时,这些步骤再次发生,本质上是重新编译了文件,新的输出反映了这些更改。TypeScript 文件如下所示。

const { calc } = $import("./calc.zig");
console.log(calc(1, 2));
Enter fullscreen mode Exit fullscreen mode

这个函数看起来像是黑魔法,直接从 Zig 导入函数,但在底层,文件被编译成共享库,并且是从库导入的,而不是从源文件本身。不过,语法看起来非常简洁易懂。我录制了这个实验原型的屏幕,并将其发布在Bun 的 Discord服务器上。

很快,Jarred 看到了这条消息并回复说我可以使用 buntime(我们将运行时称为 buntime,为什么不呢)插件 API 并将我的逻辑实现为插件,这将允许我使用 ES6 导入而不是那个看起来很奇怪的导入函数。

说实话,在此之前我从未真正使用过插件 API,所以我开始深入研究它。经过一些复杂的处理和几次重写,我终于能够将逻辑移植到插件 API 中。现在我可以轻松地使用 ES6 导入语法导入 zig 文件了。尽管 Typescript 仍然因为不知道 calc.zig 是什么或 calc 函数是什么而对我大喊大叫,但它仍然让我大吃一惊,因为它看起来令人恐惧。

import { calc } from "./calc.zig";
console.log(calc(1, 2));
Enter fullscreen mode Exit fullscreen mode

所以,我决定让它变得更恐怖一些。我添加了类型。

使用 TypeScript 的通配符模块声明功能,我创建了一个 types.d.ts,在其中我将 zig 文件路径声明为模块,并在其中添加了 calc 函数的类型定义。现在 TypeScript 运行正常,当我将鼠标悬停在 calc 函数上时,类型按预期正常工作。整个组合看起来很完美,但它仍然是一个静态原型,并不是人们可以在项目中使用的东西。我录制了屏幕,展示了这种可怕的语法,后台发生了黑魔法,甚至展示了当我将运算从加法改为减法时,变化仍然会反映出来。我为 Zig 和 Rust 文件录制了视频,然后将它们再次发布到服务器上。很快,Jarred 在 Twitter 上转发了这两个视频,展示了 bun 的强大功能。推文评论中的每个人都为看到这样的事情而疯狂。

Primeagen 评论道:“这太酷了。你有什么文章或资料可以让我读一下吗?”所以我决定为他写这篇文章 :)

有人不停地在我的Discord上发帖问我什么时候发布。很抱歉让他们久等了,但现在终于发布了!

最困难的部分是使其动态化,因为在那个原型中,我必须手动声明FFI符号定义,例如calc函数的参数和返回类型,还要手动编写类型定义。我希望尽可能地自动化,让用户在项目中使用它时能够完全控制每个方面。我希望灵活性,这让我思考如何才能最好地将这个原型变成人们在项目中实际使用的东西。我尝试了各种方法,但都在某种程度上失败了,这降低了我继续进行这个项目的动力。我放弃了这个项目很长一段时间,期间我还得兼顾大学的事情。

我的目标是,我希望用户......

  • ...轻松添加对默认情况下不可用的任何其他语言的支持。
  • ...能够用他们的自定义逻辑交换实现。
  • ...导入任何其他类型的插件,而不仅限于加载其他语言文件。

三个月后,上周我开始了从零开始的尝试。这已经是我第四次或第五次尝试从头开始重写代码了。但每次我都得从头开始,因为之前的想法都失败了。最后一次,我决定利用继承(基本上就是类)来解决这个问题。我将整个逻辑拆分成多个函数,允许用户扩展和覆盖它们,本质上就是将实现替换成自定义的实现。我花了一周时间才让一切按我的意愿运行。我最重要的目标实现了。

  • 用户可以扩展 Loader 类,并且可以使用自定义实现覆盖函数来定制行为。
  • 任何类型的插件都可以通过 hyperimport 导入。

更何况,这个想法也出现在了 Bun 1.0 官方发布视频中。点击此处观看

“我能用它吗?它在 GitHub 上吗?”

当然!查看Hyperimport 代码库并浏览wiki,获取包含指南的全面文档。

查看导入 rust 文件,获取有关设置基本超级导入项目的分步指南。

如果您有任何疑问,请随时加入discord 服务器。

如果您读到这里,非常感谢您的阅读。一个疯狂的实验想法变成了现实,背后的故事让我兴奋不已,并期待着人们能利用它并突破它的极限,创造出哪些令人惊叹的想法。

鏂囩珷鏉ユ簮锛�https://dev.to/tr1ckydev/hyperimport-import-c-rust-zig-etc-files-in-typescript-1ia5
PREV
设计师可以在 Dribble 或 Instagram 以及 Awwards 网站上关注,寻找 Web UI 灵感
NEXT
了解命令行:基本 Git 命令