-
Notifications
You must be signed in to change notification settings - Fork 301
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
MSBuild/NuGet integration: custom TargetFramework #3549
Comments
@jwosty Can additional flags be passed to the <OtherFlags>$(OtherFlags) --lang TypeScript</OtherFlags> |
Question 3 is especially tricky. On the one hand, there is some relationship between Fable versions and .NET SDK versions, because Fable implicitly supports a certain subset of APIs from some actual version of .NET (and lower). For example, .NET 8 will add various APIs which .NET 7 doesn't have, and Fable may choose to support some of these new APIs, so the Fable SDK would have to choose some .NET version to base itself off of. On the other hand, say you're writing a project and you want to reference a Fable-compatible package. You really should only need to know the Fable version it targets; the .NET version which was used to built it is irrelevant, I think. Either way, the tooling (mainly IDEs) needs reference assemblies to work properly. In the POC, I just turned those off completely, which is probably why intellisense is completely broken. So Fable really should define some reference assemblies, which it could do in one of a few ways:
In my opinion, this last one makes the most sense and is the most appealing, but is obviously also the most work. I have no sense for how much work but it seems big. But also awesome. If we did that, it might even make intellisense understand which methods are and aren't supported by Fable, at compile time! |
@ncave I did built in support for exactly that - However in terms of other target languages, I'm playing with overloading EDIT: Now that I think about this, I'm not sure whether or not that will be the correct approach. An alternative would be for there to be a TargetFramework for each langauge, for example |
Another point to investigate: Is it possible, and feasible, to make project references between non-fable (think: ASP.NET) and fable projects work? It's already possible in the vanilla .NET SDK to use That being said, this is another problem which we don't have to solve right away, or even at all. It's a nice-to-have DX (developer experience) feature which could reduce friction for newcomers. We could just wash our hands of this complexity and just leave it as the user's problem. |
Thank you for opening this issue and taking time to investigate on this feature. It can seems obvious, but for such a feature to be included in Fable, it is important that the maintainers understand it. This is because it will probably fallback on us to maintain it and make it evolve over time. I don't really know much about all the References, Runtime, MSBuild stuff. The first time this feature was introduced to me, it seemed like the goal was to allow different packages to be used based on the Target language or if we target .NET vs Fable. An example, for this situation is for example Thoth.Json and Thoth.Json.Net However, to me it seems like this is complete new toolchain that is being proposed. Meaning that the user would not invoke Fable CLI anymore, but use MSBuild to configure a project which would in turn invoke Fable CLI or new entry point of Fable?
This kind of request is something that @nojaf asked about in the past. My answer at the time, was that I didn't want to change how Fable CLI works yet. As it would split the focus and makes things more complex. But if we are do think about it of MSBuild integration this is also an opportunity to design it in a way that can be used for that. @nojaf Could you please open an issue about what you wanted to do? |
@MangelMaxime Everything you're saying makes sense. If it helps, I'd be happy to own the SDK part of this and be responsible for updating/maintaining it. This could be a good reason for it to live in a separate repo, to make the boundary clear. Only minimal changes to this repo would have to be made.
Yes, you are correct - if the full version of this proposal were completed, targeting would be very clear, since the .NET tooling would be made aware of this stuff. For example, instead of there having to be both a <!-- MyApp.Model.fsproj - would be able to be referenced both from .NET projects and Fable projects -->
<Project>
<PropertyGroup>
<TargetFrameworks>netstandard2.0;fable4.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Library.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Elmish" Version="X.Y.Z" />
</ItemGroup>
</Project> This is one of my biggest pain points when working with Fable (both apps and libraries), personally, so this would be a huge win. A Fable user would also be able to tell canonically whether a package is Fable-compatible by looking at its target frameworks.
Yes, that is one proposition. I'm mostly painting an overarching idealized vision, but we don't have to do everything all at once; it can be incremental. But yes, if this piece were done, the user would not have to invoke the Fable CLI to build a project; they would simply use
For what it's worth, changing the CLI is not the only way. If I could reference Fable as a library, and had some function I could call to perform compilation given all of the compilation inputs (source files, project references, whatever else it needs), I could also make that work. EDIT: I realized I already made this last point |
The more I think about it, the more I realize that the target language should probably be part of the TargetFramework rather than the RuntimeIdentifier. I'm not sure if NuGet takes the latter into account when resolving package compatability. If so, I favor RuntimeIdentifier. If not, a package intended for Javascript Fable wouldn't be able to prevent a Python Fable project from referencing it, so it'd have to be in TargetFramework. If we go with TargetFramework, there should probably be a "root" |
Here's an interesting relevant discussion on WASM/browser TFMs on NuGet: NuGet/Home#8186 |
Hi there, This is a very interesting proposal. Being able to re-use the NuGet restore from the SDK sounds really interesting. It could also be interesting if this worked with
Yes, this is what I mentioned in the past as well. Having Fable as a dotnet library would be beneficial. I'll create a new issue for that. |
Really interesting! I've been working on a reasonably complex web app in Fable for the past year or so and have built a lot of complex Typescript/React apps before that. I suppose firstly I should say that all these observations are based on building web apps in Fable, and treating it essentially as a replacement for Typescript in a front end web stack. I feel this is perhaps the best route for Fable to (continue to) take, because its such a good fit there, and the target runtime is reasonably well constrained. I'm still somewhat unsure if I would use Fable in, say, a Rust project, There are non-zero costs involved, dealing with library bindings and non orthodox setups. I think I would rather just use Rust and benefit from the community etc. What makes it so compelling as a web tool, is that I know for certain that I would want to use it over JavaScript, even with all the burdens and overheads. Anyway, some thoughts based on that and the setup I'm currently using..
And some concerns:
Anyway, all that being said, I think that having a custom target for Fable is a great idea, and being able to reference projects and perhaps even have multiple outputs (for doing stuff like web workers) would be great, but I think, at least for now, it's better to keep the division between the dotnet side and the messy 'web' side as far as tooling goes, and instead address that though guidance and templates. So you could have a template for a 'Fable SPA Webpack' project, or a 'Fable Esbuild with Yarn' project etc. We could even look at going down the same route as Create React App, with a Create Fable App equivalent, which would provide a front end experience and generate and maintain artefacts like package and bundler configs for you. But you would have the option to 'eject' if you need to. The difference being that all of this stuff is not baked into the Fable tool, rather that would be just one part of the stack that CFA would manage for you, etc. |
Related: #2338. |
Great insight @robitar! I would like to add that when using Fable for the web, I really dislike that I'm forced to take the In the example of I have the same experience with Vite for example. Imagine using JSX, Sass, TypeScript and Markdown. When using Fable, we always need to compile it first so that whatever comes after can pretend Fable doesn't exist. While I for sure understand that creating a plugin for esbuild and Vite might be challenging, from a developer UX point of view I strongly believe this makes sense. At the same time, I do very much see the appeal of what @jwosty is proposing here. Doing a I hope we should extract a lower-level component (see #3552) so that both options can be explored by the community. |
This is actually really interesting, because I'm the opposite, personally - I dislike being forced to learn javascript bundlers since I come from the .NET side. Honestly I didn't consider the fact that some people may prefer the JS tooling - fair enough! XML is pretty gross. I would hate to force people to drop down to MSBuild scripting when they're trying to avoid it, so how about this: we build this thing in more than one layer, making sure each layer stays very separate from higher layers. There'd be the lowest level - the Fable CLI (which already exists) and/or the Fable library (as proposed by #3552), which would allow for any kind of build solution you could come up with, be it webpack, make, FAKE, plain ol' shell scripts, etc. Then, there'd be a library providing just some MSBuild tasks, let's call it OK, so this addresses the build side. However, the other half of this suggestion is to improve the NuGet package creation and consumption story, and I'd love to hear your thoughts on that, too. I'd still argue in favor of having custom Fable TFMs recognized by NuGet. In a perfect world, all Fable-compatible packages would have a target of It's also certainly an option to make sure the Fable.MSBuild.Sdk has a way to only import it for the target framework and NuGet restore capabilities, without injecting its custom targets for Fable compilation. |
Also - just want to point out that it's probably important to decide whether or not we want at least the NuGet TFM, because I'm fairly certain we'll have to work with MS to make that happen. If @MangelMaxime approves, I'll be happy to file an issue over at NuGet/Home to get that ball rolling. (also @baronfel will be a help) Here are some examples of past requests for new TFMs: NuGet/Home#10800, NuGet/Home#9347, NuGet/Home#8186, NuGet/Home#7773 |
Yea makes sense, especially when starting its quite confusing, and you are really on your own. However, with the setup I have, I never interact with
My big concern here would be performance. One of the main advantages of esbuild is that its so fast, it doesn't even show up on the console, even for a fairly large project (the output from Fable is about 800kb of JS). I'm also not sure how this would actually work, what would 'pull in' the F# project? An index.js somewhere?
I think the thing there is that these technologies use file based imports to build up the dependency tree, whereas F# relies on a separate project, which makes file by file inclusion tricky, you definitely wouldn't want to be parsing the project for each file. There was a language request a while back to move F# to an 'import' style model, but obviously that might be a long way off it ever.
Yes agreed, having some solid underling technology would be great, something akin to Script# - you take a dotnet based project and instead of outputting IL it outputs JS, and has some reasonable tooling out of the box for minification/watching, but if you want to use industry standard stuff for that, you can. |
Keep in mind that it's possible to use dotnet tooling for the initial F#->JS compilation, then a JS bundler after that. For example, in this sample project (from Fable.MSBuild.Sdk), you invoke the build at the highest level with Also, I'm taking that exact philosophy of ".NET is to IL assemblies as Fable is to JS" while designing the SDK. There should probably be some more samples out there demonstrating these other ways to set up builds. It really is not so clear when you're first learning. |
Well just for the record I don't actually 'like' using all the npm stuff 😄 and it was a slog to learn it over the years, this is a matter of pragmatism. If you are building a real-world app, after a certain point, you can't pretend like all this stuff doesn't exist, unless you like reinventing wheels all the time! When I started out down this road, I too wanted to ditch npm and friends, and even experimented with a tool which would automatically pull in packages and produce ts2fable bindings on the fly. But completely supplanting the npm ecosystem like this is not straightforward. It can surely be done, but its going to be a significant undertaking. There's already a reasonable penalty to using Fable - you are off the beaten track and you need to deal with writing your own bindings. F# is worth it for me, no question, but I'm not really going to turn my back on the entire ecosystem either. Using the standard tooling means you have one less thing to translate and manage. I suppose I would also just say, on a more philosophical note perhaps, that I think it's better to actually accept that you are indeed building a web app, using web technologies etc, and not try to sweep this under the carpet. It's probably easier for me, I started out in web and I've never 'hated' JS/CSS etc, even in the dark days, although it was certainly a lot hacker than it is today! The way I see it is: Fable is a way to use F# to make browser apps based on native web technologies, which is a slightly different proposition to: Fable is a way to convert F# to JavaScript. Once I accepted this, I knew where I stood. I think this distinction is even more relevant now that you have Blazor/Bolero. To my mind, it makes the value proposition even clearer, and more distinct. If you want a full dotnet experience, use Bolero, if you want to deeply integrate into the web ecosystem, use Fable.
Agreed. I think this very reasonable.
The TFM looks good to me, it would also mean, presumably, that you would have a more robust way to find Fable packages on Nuget directly, instead of having a separate package index. I probably don't fully understand the drawback there, is this because you may want to have a single project which includes Fable and regular CLR packages? |
This makes sense, and I think would be a great bedrock, I guess I'm overepmhasising the web nature of things, there are certainly non-web JS environments and having a sensible and well ordered tool chain for that would be very nice. I've mentioned Script# a few times here, looks like its still around, sort of: https://github.com/nikhilk/scriptsharp I used this way back in the day, and it covers more or less the same ground. It manifested as a regular C# project, and leaned on all the integrated .NET Framework tooling (including Visual Studio). It output both dlls and script, so you could reference one library in another, which made the workflow feel exactly like a regular CLR build. I think we can take that example and apply it more or less to Fable and hopefully get similar results. It would be nice to have multiple projects organized into a solution, some Fable, some not, reference packages (some Fable, some regular) and have everything just work out of the box!
Absolutely. If I were new to web development, or not strongly motivated to use F# etc, I wouldn't have stuck with it at all. There are a few samples I think, but what's really lacking is real world stuff that's up to date. I'd be happy to help out there perhaps, with a sample of a real world end to end toolchain, although it probably makes sense to see what we want to aim for/change first. |
The plugin would probably need to have the project as input. However, would only need to be processed once and I don't think this is any different from how it works today. I don't overly see performance as the bottleneck just yet. It will bring its own slew of challenges for sure. |
Just want to note that there are two main possibilities for a TFM design: Option A: (Fable + Target lang) as TFMThis would look like: Pros:
Cons:
Option B: (.NET TFM) + (Fable + Target lang)This would be the MAUI-style naming, like: This would look like: Pros:
Cons:
Either way, the compatibility rules would looks something like:
And perhaps an additional compatibility rule allowing anything to reference a Fable project as content? Though that might be more an SDK concern than a NuGet concern, and I'm not sure on the specific mechanisms, but that could be interesting to investigate. |
Here is a summary of our discussion @nojaf @jwosty Please feel free to complete or correct me NuGet integrationWe would not go with Option B because currently, Fable is not tied to a specific version of .NET SDK. The convention is that Fable always support a subset of the .SDK/BCL APIs. The available APIs are controlled by the target framework / packages used. And then Fable will either succeed in replacing it or print an error message. And we can add support for more BCL APIs in a minor version of Fable. With that said, we would probably go with Option A but by using full language names for consistency like:
We can contact the NuGet team, not to ask them to add them right now. But more to have guidance of if this is possible and what are the requirements / drawbacks. Example of questions we have for them or internally:
@jwosty as you seem more familiar with the NuGet/MSBuild stuff, would you be ok to contact the NuGet team? MSBuild integration
|
Although this may be technically true, I don't think this is semantically right. Fable does track against F# language features and types which change with dotnet versions (types such as It's a subset, yes, but its still a strongly coupled subset. I think this is more in keeping with the spirit of platform specific TFMs. The Fable runtime is just an extremely constrained platform. |
I would be leaning more towards Option B, for the reasons outlined by @jwosty but also as mentioned above, it seems more practical to acknowledge that there is a clear coupling at least in terms of tooling, but also in terms of language features. I would prefer to see Fable become more 'normalized' in the dotnet ecosystem, and it seems that setting it up as this weird isolated thing with no relation at all to dotnet proper would be moving in the opposite direction. |
Problem with option B, is that it requires new TFM for each .NET release and makes documentation and authoring library more complex for author libraries. For example, I am using F# since 7+ years and don't understand all the implication that NuGet integration yet. In any case, the questions we plan to would be the same if we go in this direction. For now, we are mostly exploring ideas, feasibility, features and drawbacks if we were to add TFM for Fable. It is also important to note that TFM support here is mostly to have the ability to allows Fable libraries to have different dependencies depending on the targeted runtime (JavaScript, Python, etc.). It is not really coupled to which version of Fable you use are using to compile the project. This is still control by the user when installing |
For what it's worth, it probably would control the version of the Fable tooling used, or at least have some relation to it. If we do both NuGet and MSBuild integration, the Fable user wouldn't need to install the CLI tool at all. Referencing the Fable SDK instead of <Project Sdk="Fable.Sdk/0.0.1">
<PropertyGroup>
<TargetFrameworks>fable4.0-javascript;fable4.0-typescript</TargetFrameworks>
<NoWarn>NU1701</NoWarn>
</PropertyGroup>
<ItemGroup>
<Compile Include="App.fs" />
<Content Include="webpack.config.js" />
<Content Include="public\fable.ico" />
<Content Include="public\index.html" />
</ItemGroup>
</Project> Obviously it doesn't prevent you from also installing the CLI tool; it's just that you wouldn't need to |
Yea having a 'real' SDK setup seems much more useful to me, rather than just having a smart tag to filter nuget references (which is kind of useful, but probably wont have as much impact on fostering Fable usage as this would). It also touches what I was trying to express above. Even if there isn't a runtime relationship with the CLR/dotnet, there's a definite tooling/dev dependency with the dotnet SDK, and it seems that having a proper integration at that level is actually more relevant than trying to define the relationship with Fable/CLR in terms of target platforms (although that is still beneficial, it's a more complicated knot to untangle). |
That's actually a good point @robitar - any custom MSBuild SDK which hooks into concepts from the .NET SDK (like target frameworks) implicitly have a dev-time dependency on it. This Fable SDK wouldn't make sense in the absence of the .NET SDK, and probably a particular version, too. I'm not sure what guarantees .NET makes in terms of backwards-compatibility of MSBuild targets, but at the very least it would probably implicitly depend on a minimum version of the .NET SDK. Though once again this may be more of a concern of the actual SDK itself, rather than packages built with the SDK. Consider this hypothetical: Say there's (at least) two versions of the Fable 4 SDK, one which works with .NET 7 and one with .NET 8. Also, say we're authoring the |
Broadly, as long as you're interacting with a Target, Item, or Property whose name doesn't start with |
Alright, I've started a thread to ask about a NuGet TFM: NuGet/Home#12965 |
@jwosty how would one download the Fable SDK |
@nojaf the statement itself downloads it. https://learn.microsoft.com/en-us/visualstudio/msbuild/how-to-use-project-sdk?view=vs-2022#how-project-sdks-are-resolved |
Hey all, we've got a discussion going with the NuGet team over at NuGet/Home#13977 |
Description
There should be a Fable MSBuild SDK (read: https://learn.microsoft.com/en-us/visualstudio/msbuild/how-to-use-project-sdk?view=vs-2022) which provides tasks and targets to compile Fable code. If this were done, setting up a Fable project could be as simple as referencing the SDK, setting
<TargetFramework>fable</TargetFramework>
, then runningdotnet build
.A proof-of-concept of exactly this grew out of a discussion we had in the FSSF Slack channel: https://github.com/jwosty/Fable.Sdk
This POC mainly answers the question: "Can we integrate Fable into the dotnet build process, with its own TargetFramework, and make it work with built-in MSBuild mechanisms (like package restoring)"? I think this shows the answer is: Yes. The main question left is whether or not NuGet package support is possible without NuGet having to know about a Fable TFM (see: section below)
Technical details
Some better integrations with the Fable compilation process would be awesome, and would allow for a much better implementation of this SDK. Currently, the POC accomplishes compilation through a
FableCompile
task which just runs the Fable CLI. For example, if Fable either exposed some of its internal functions which perform compilation publicly, or if the CLI allowed you to manually provide all the inputs (F# source files, package references, etc, a lafsc
), then the SDK could completely bypass having to deal with an extra fsproj (no need for Buildalyzer when we have the real thing!).(Related: #3280)
Open questions
Below are some unanswered questions, as well as some design decisions that could go one of multiple ways:
fable
), or should it extend a .NET TFM (i.e.net8.0-fable
, likenet8.0-mac
? There are pros and cons to each.fable watch
and hot reload (via webpack-dev-server for example) fit in?The text was updated successfully, but these errors were encountered: