Hyperimport — импорт файлов c, Rust, Zig и т. д. в TypeScript.
— Что? Я правильно прочитал заголовок?
Да! Давайте сразу перейдем к примеру, почему бы и нет?
Ты сможешь это сделать,
index.ts
import { add } from "./add.rs";
console.log(add(5, 5)); // 10
add.rs
#[no_mangle]
pub extern "C" fn add(a: isize, b: isize) -> isize {
a + b
}
И даже больше, например, импорт собственных функций C из libc в машинописный текст. Ознакомьтесь с руководством.
«Подожди! Что?? Как это вообще возможно!?»
Давным-давно, когда я работал над проектом webview-bun, который по сути является FFI
-оболочкой API-интерфейсов библиотеки веб-просмотра для Bun. Мне случайно пришло в голову, почему я не могу импортировать исходный файл C веб-просмотра непосредственно в машинописный текст, если через FFI API мы можем импортировать функции из общих библиотек, которые по сути скомпилированы из исходного файла, такого как c, rust
, zig
и т. д., как насчет того, чтобы создать способ связать функции импорта в машинописном тексте с исходным файлом и автоматизировать промежуточные шаги таким образом, чтобы конечный пользователь видел, что они напрямую импортируют из исходного файла, в то время как все сложные части управляются автоматически внутри.
Я написал простую функцию Calc
в Zig
, чтобы складывать два числа. В машинописном скрипте я написал функцию импорта, которая будет определять путь к этому файлу zig
, запускать дочерний процесс для вызова компилятора zig
для компиляции этого файла в файл общей библиотеки, затем открывать эту общую библиотеку с помощью API FFI
и возвращать символы, которые, по сути, содержит функцию расчета. Поэтому, когда я использовал функцию для импорта ZIG-файла, эти внутренние шаги происходили незаметно, и функция Calc
работала. Затем, когда я изменил операцию внутри функции zig
с сложения на вычитание и выполнил файл typescript, эти шаги повторились, по сути, перекомпилировав файл, и новый результат отразил изменения. Вот как выглядел файл typescript.
const { calc } = $import("./calc.zig");
console.log(calc(1, 2));
Казалось, что в этой функции используется черная магия: функция импортируется непосредственно из Zig
, но внутри файл компилируется в общую библиотеку и импортируется из библиотеки, а не из самого исходного файла. Но синтаксис выглядел очень чистым и простым для понимания. Я записал свой экран, демонстрирующий этот экспериментальный прототип, и разместил его на дискорд-сервере Бана.
Вскоре Джаред наткнулся на сообщение и ответил, что я могу использовать Buntime (мы будем называть время выполнения Buntime, почему бы и нет) Plugin API и реализовать свою логику в виде плагина, который позволит мне использовать импорт ES6
вместо эта странная функция импорта.
Честно говоря, до этого я никогда не использовал API плагинов, поэтому начал погружаться в него. С некоторыми изрядными сложностями и некоторыми переписываниями мне наконец удалось перенести эту логику для использования API плагинов. Теперь я мог легко импортировать ZIG-файл, используя синтаксис импорта ES6
. Несмотря на то, что Typescript все еще кричал на меня, потому что он не знает, что такое Calc.zig
и что такое функция Calc
, он все равно поразил меня, потому что выглядел устрашающе.
import { calc } from "./calc.zig";
console.log(calc(1, 2));
Поэтому я решил сделать его еще более ужасающим. Я добавил типы.
Используя функцию объявления модуля с подстановочными знаками в typescript, я создал файл typescript.d.ts
, в котором объявил путь к файлу zig
как модуль, внутри которого я добавил определения типов для функции Calc. Теперь машинописный текст доволен, и когда я навел курсор на функцию Calc, типы работали отлично, как и ожидалось. Вся комбинация выглядела идеально, но это все еще был статический прототип, и люди не могли использовать его в своих проектах. Я записал свой экран, демонстрирующий этот страшный синтаксис с черной магией, происходящей в фоновом режиме, и даже показал, что когда я менял операцию с сложения на вычитание, изменения все равно отражались. Я записал файлы как для Zig, так и для Rust и снова разместил их на сервере. Вскоре Джаред разместил здесь в Твиттере оба видео, демонстрирующие силу булочки. Все в комментариях к твиту сошли с ума, увидев, что такое вообще возможно.
Праймаген прокомментировал: «Это очень круто. Есть ли у вас какие-нибудь статьи или что-нибудь, что я могу прочитать по этому поводу?», поэтому я решил написать эту статью для него :)
Люди продолжали раздувать мой дискорд, спрашивая, когда я его выпущу. Прошу прощения, что заставил их ждать, но он наконец-то вышел!
Самая сложная часть заключалась в том, чтобы сделать вещи динамичными, потому что в этом прототипе мне пришлось вручную объявлять определения символов FFI, такие как типы аргументов и возвращаемых значений для функции Calc, а также вручную писать определения типов. Я хотел, чтобы все было максимально автоматизировано, и чтобы пользователь имел полный контроль над каждым аспектом, когда он будет использовать его в своем проекте. Мне нужна была гибкость, которая заставила меня задуматься о том, как лучше всего превратить этот прототип в настоящую вещь, которую люди будут использовать в своих проектах. Я экспериментировал с различными подходами, но все они в чем-то потерпели неудачу, что снизило мою мотивацию к дальнейшей работе над этим проектом. Я забросил это на долгое время, а между тем у меня были университетские дела.
Моя цель заключалась в том, чтобы пользователи могли
- Добавить поддержку любого другого языка, который недоступен по умолчанию.
- Иметь возможность менять реализации со своей собственной логикой.
- Импортировать любые другие плагины, а не ограничиваться загрузкой других языковых файлов.
Три месяца спустя, на прошлой неделе, я начал с нуля. Это была моя четвертая или пятая попытка переписать книгу с нуля. Но каждый раз, когда я начинал все заново, у меня был опыт старых неудачных идей. В последний раз я решил подойти к этому, воспользовавшись преимуществами наследования, в основном классов. Где я разделил всю логику на их функции, позволяя пользователю расширять и переопределять их, по сути, заменяя реализации своими собственными. Мне понадобилась неделя, чтобы все заработало так, как я хотел. Мои самые важные цели достигнуты.
- Класс
Loader
может быть расширен пользователем, а функции могут быть переопределены с помощью пользовательских реализаций для настройки поведения. - Любой тип плагина можно импортировать через hyperimport.
Не говоря уже о том, что эта идея также была представлена в официальном видеоролике о запуске Bun 1.0.
Смотрите здесь.
«Могу ли я использовать его? Это на GitHub??»
Абсолютно! Посетите репозиторий Hyperimport и просмотрите wiki подробную документацию с руководствами.
Ознакомьтесь с разделом Импорт файла ржавчины, чтобы получить пошаговое руководство по настройке базового проекта гиперимпорта.
Не стесняйтесь присоединяться к серверу Discord, если у вас есть какие-либо вопросы.
Если вы зашли так далеко, большое спасибо за прочтение статьи. История сумасшедшей экспериментальной идеи, воплотившейся в реальность. Я очень взволнован и буду с нетерпением ждать, какие умопомрачительные идеи люди смогут использовать и расширить его возможности.