2023-05-03 13:00:00
Native AOT libraries with TypeScript
This is part of a series on Native AOT.
Previous -- Top
A few weeks ago I published some samples showing the use of Native AOT libraries from Rust. As I mentioned, the projection of .NET APIs to Rust requires quite a few ergonomic compromises. For example, the following line in C# is a call to the QuestPDF method to set the size of a page:
page.Size(PageSizes.Letter);
But the Rust projection is messier:
page.Size_PageSize(&PageSizes::get_Letter()?)?;
Rust doesn't have method overloading by name, and the
Size()
method has multiple overloads, so we have to give each one its own name, likeSize_PageSize()
.Rust doesn't have exceptions, and every .NET method might throw, but the metadata doesn't tell us anything about which kinds of errors we might want to propagate or not. So for the moment, the binding generator outputs every method with a return type of
Result<>
, which means we need to use?
after every call.
Rust also lacks other things that .NET wants, including optional parameters, property getters/setters, and inheritance. Because of all these differences, Rust for general .NET development would typically involve too much pain to be practical.
But the nice thing about Native AOT libraries, is that they can be used with anything that can call C.
Hello TypeScript
TypeScript has method overloads, exceptions, property getters/setters, and inheritance. Hmmm.
For the last few weeks, I've been adding TypeScript as another output language for my binding generator. Things are in a rough state, but the results look positive, so I have published a nuget package and a working port of the QuestPDF sample. That sample is short, but it involves things like generic delegates and extension methods, so I consider it non-trivial.
Interop between any two languages will always involve trouble spots, but TypeScript can look awfully similar to C#. Here's a visual diff from the Quest PDF sample, with TypeScript on the left, and C# on the right:
All of the differences here are because I am projecting nullability more strictly than it is in C#. I currently think this is a feature rather than a bug, but I'm not completely settled on that, and if I stopped doing it this way, these two snippets would actually be identical.
To be fair, let's acknowledge that this is just one sample. TypeScript lacks several things that require extra effort for .NET interop, including:
byref parameters
extension methods
runtime type information
I mean, if TypeScript were exactly like C#, it would be C#.
Still, I see potential here.
Details
The binding generator itself is contained in a nuget package:
https://www.nuget.org/packages/SourceGear.Bridge.NativeAOT.TypeScript/0.6.0
The QuestPDF sample is in the samples/typescript
directory of this repo:
https://github.com/sourcegear/bridge-info
For this prototype, all the FFI-level stuff is setup for Deno (apologies to fans of node).>
Related
There's something going on in the following repo:
https://github.com/microsoft/node-api-dotnet
I'm not super-clear on where that project is headed. And there are significant differences relative to what I'm doing, but also some common ground. I think it looks interesting.
Next steps
I'm hoping that I will soon have an AvaloniaUI sample (it's a fair bit more complicated). Down the road a bit, I'd love to get this working with [at least the desktop versions of] MAUI, but I'm not sure yet what will be feasible.
If you have any questions or feedback, please feel free to post in the Discussions or Issues area of the sourcegear/bridge-info repo linked above.