-
-
Notifications
You must be signed in to change notification settings - Fork 37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Typescript generation through oxc #197
Comments
I don't think this aligns with my vision for Specta but I do think you've got a couple of options. In terms of I personally think it's out of Specta's scope to deal with advanced types such as classes. With the Specta release candidate releases, I have been refactoring However, I think Specta can be the perfect building block for these sorts of abstractions. A great example of this is tauri-specta. Specta's core has a type called Due to this fact, Specta doesn't really have function exporting built in, it's on the downstream user to define how to convert a With all of this in mind, I think Specta's core types can be composed to express Typescript classes (similar to how use specta::datatype::{DataType, FunctionDataType};
pub struct TsClass {
name: String,
fields: HashMap<String, TsClassField>,
methods: HashMap<String, TsMethod>,
}
pub struct TsClassField {
ty: DataType,
default_value: Option<String>, // Alternative this could be `specta::datatype::LiteralType` but it would limit the ability to express something like a function call or `new Date()`, etc.
}
pub struct TsMethod {
func: FunctionDataType,
body: String, // You could potentially create better abstractions for expressing JS/TS code but `String` ends up being the most flexible.
} On a different note, it is also worth noting that Specta is not tied to any exporter. It's Long term I intend for language exporters to move out of the core Something you did bring up is Sorry for the long explanation and feel free to ask any follow-up questions if anything I said was unclear! Also, feel free to jump in the Discord if you do start working on anything in this space as questions are always welcome! |
Thank you so much for such a detailed and thoughtful answer! The reason why I explored What I mean specifically is, we turn the creation of a TypeScript representation instead of a string into: pub(crate) fn datatype_inner<'a>(
ctx: &ExportContext<'a>,
typ: &DataType,
type_map: &TypeMap,
) -> core::result::Result<TSType<'a>, ExportError> {
Ok(match &typ {
DataType::Any => ctx.ast.ts_any_keyword(SPAN),
DataType::Unknown => ctx.ast.ts_unknown_keyword(SPAN),
DataType::Primitive(p) => {
let ctx = ctx.with(PathItem::Type(p.to_rust_str().into()));
match p {
primitive_def!(i8 i16 i32 u8 u16 u32 f32 f64) => ctx.ast.ts_number_keyword(SPAN),
primitive_def!(usize isize i64 u64 i128 u128) => match ctx.cfg.bigint {
BigIntExportBehavior::String => ctx.ast.ts_string_keyword(SPAN),
BigIntExportBehavior::Number => ctx.ast.ts_number_keyword(SPAN),
BigIntExportBehavior::BigInt => type_reference(&ctx, BIGINT),
BigIntExportBehavior::Fail => {
return Err(ExportError::BigIntForbidden(ctx.export_path()));
}
BigIntExportBehavior::FailWithReason(reason) => {
return Err(ExportError::Other(ctx.export_path(), reason.to_owned()));
}
},
primitive_def!(String char) => ctx.ast.ts_string_keyword(SPAN),
primitive_def!(bool) => ctx.ast.ts_boolean_keyword(SPAN),
}
} because we're now working with an AST instead of a string, we're able to manipulate it easier and malformed types are less likely to happen. Typescript types can get quite complicated and this would be kind of an escape hatch. Then again: the amount of dependencies increases quite a bit and I do not know if this is worth the projected benefit, but it could make future features a lot easier. With the function definition, what I was looking at specifically was
Yes! I have been playing around with the DataType quite a bit to try to fit it into my specific model (although I feel like some of the things I have been doing could be considered illegal), and I love the simple representation of all types.
Totally, I didn't create the issue in hopes that they will be added (I don't really see how), but it may be an additional point towards language specific metadata that might be needed (I believe there was an issue about
I think it's mostly about convention. The current project I am working on uses interfaces more than types, and I know that |
regarding the let mut file = File::new();
file.export::<Example>();
file.export_datatype(xyz);
file.import::<Example2>("./relative-path.ts");
for ty in type_map.iter() {
file.define(ty)
}
file.signature_fn(func1);
let mut buffer = vec![];
file.export(&mut buffer);
println!("{file}")
This would have several upsides:
This was more of a spitballing idea than anything else, but thought it might be useful as it would/could unify the API interface, not only for typescript, but also across languages. |
Oxc is very cool but it being a heavier dependency definitely scares me away from using. The
I think the general reasoning behind not supporting it was that a function type means nothing if the function isn't implemented. If I had to guess though you want the types to be exported while leaving the implementation up to bindgen? If this is the case I would be happy to support this - #201.
Yeah, I have been generally on the fence about supporting #110 but I think it will happen because it's kinda required to do all sorts of low-level stuff.
You should be able to export a header on your file which will disable any linting tool, I personally think codegen stuff should be exempt anyway. In terms of performance as far as I am aware it doesn't make much of a difference. This video by Matt Pocock is a great watch and discusses this topic. It is 1 year old but I would be surprised if the recommendation has changed. That being said the recommendation you linked is specifically about extending interfaces instead of using unions so idk. Supporting both would mean two entirely different code paths which I think would be a little too much extra to maintain, especially needing to duplicate a lot of test cases. This could be a custom exporter but personally I think just sticking with
Oh, whoops I completely misread this as JS/browser I do like the idea of some high-level APIs in Specta for collecting types but I am still unsure where the draw the line. I wouldn't be suprised if for Specta v2 we have a You may have noticed Specta's APIs are very low-level at the moment and this is because Specta was originally developed for rspc where high-level APIs weren't required but now that it has become a completely separate project more work does need to be done to make it more user-friendly. It's also worth noting that Specta needs to keep a pretty stable API surface. If I want to make a breaking change to any function I have to do a major release. Doing a major release will break the |
Exactly, and my (naive) thought is/was that essentially what we would do is an AST translation between the different languages. Just spitballing, but for example (again these are on the heavier side)
I could imagine that this could make developing exporters for some languages easier, and potentially allow us to unify using a trait interface, although due to the differing nature of all these languages I am unsure how beneficial this would be. In any way, if this is worth exploration, which I am unsure about, it shouldn't be done in
Correct, thank you for #201 , this was exactly what I was looking for.
Yes, I think this is more a matter of taste. This also isn't something I really strongly believe about, just something I was a bit surprised by at first glance. I also believe that tools like // interface Fruit {
// flavor: string
// }
type Fruit = {flavor: string};
interface Apple extends Fruit {
origin: string
}
interface Orange extends Fruit {
color: string
}
type Persimmon = Fruit & {seeds: number} uncomment |
I have a local branch with some experimentation, which replaces the current exporter with one based on
oxc
. The idea behind usingoxc
or similar is that while heavy on dependencies, it would allow us to create significantly more complex types more easily without worrying about creating valid typescript.(For example, it would allow us to generate exported interfaces instead of type aliases)
If interested, I'd happily polish this (add tests, etc) and upstream the changes.
(This would also allow us to have a
File
object, and you add all statements to it one by one; it would also allow us to easily add functions to the export)The text was updated successfully, but these errors were encountered: