ProductiveRage / Bridge.React

Bindings for Bridge.NET for React - write React applications in C#!
MIT License
74 stars 14 forks source link

Declarative Markup and sharing code with Typescript? #50

Closed kendallb closed 5 years ago

kendallb commented 5 years ago

One of the things I like a lot about writing JSX or TSX files is the declarative markup used to build the DOM in the render functions. It makes it easy to read and understand at a glance. With Bridge.React you end up writing the DOM code using functions which is not as clean IMHO and much harder to read (and probably a lot more noisy than JSX or TSX).

Alas I can’t think of any way currently that something like JSX could be supported with C# out of the box. Something like Razor but the opposite is needed where code is the default and markup is embedded in it rather than markup embedded in code as it is in razor.

Anyway, it occurred to me that since it’s good practice in React to write all your DOM code as pure components anyway and render only props, perhaps there is some clean way to write the core logic code in C# using Bridge.React but call across to regular Javacript or Typescript components to do the actual rendering? Not sure how you could do it but somehow have the Typescript code in the same project as the C# code and they know about each other? Or maybe the TSX code is in the Web project but Bridge somehow knows about it?

Do you think that is even feasible?

I really want to convert to Bridge.React and write C# for the browser but I am stuck on having to give up using declarative markup for the DOM as writing it in code makes it a lot harder to visualize the final HTML that ends up in the browser.

ProductiveRage commented 5 years ago

I can't see any way to write JSX/TSX-style declarations with Bridge. But I have also considered in the past combining TypeScript with Bridge in some manner and writing the core application logic in C# / Bridge but having TypeScript components. Bridge can emit TypeScript definitions for its classes and so calling Bridge code from TypeScript should be feasible in a type-safe manner (I've never put this to the test myself). I suspect that you would want a separate project for your TypeScript code, so that the Bridge code is built in one project and is built using the Bridge compiler and the TypeScript is in a separate project and built using whatever tooling and configuration that you want to use there.

It might then come down to a question of whether the TypeScript project is the "host" (and for it to call into the Bridge library to do work) or whether the Bridge project is the host and it will need to call the TypeScript components somehow. If it's the latter than you could either call the TypeScript components directly by emitting JavaScript from within wrapper methods in C# or you could create some Bridge bindings to your TypeScript library, in a similar manner to which Bridge.React provides bindings to the JavaScript React library. There is a little bit of information about writing bindings to external components at the bottom of the README for this project (under "Using third party / non-Bridge.NET components") and you shoud be able to find out more information from the main Bridge site. I imagine that one challenge would be that you don't want to duplicate all of the typing information from the TypeScript components in the Bridge project! You want bindings just to the top level components that you want to call from C# (if you choose to have a Bridge host project).

kendallb commented 5 years ago

Yeah I think there is some way to make it work in theory, but I suspect in practice the overhead of managing the typings for both sides of the code fence would probably not be worth the extra effort. What we really need is some way to be able to compile the Typescript TSX files directly into the bridge project, but for that you would need to teach the Bridge compiler about Typescript. Probably a massive project in it's own right.

Gah. I guess I might just have to write the DOM code in code, rather than it be declarative. Typescript is just so different to C# code that I feel like I would not be that productive writing Typescript code at the same time as writing C# code and would be more productive writing regular Javascript JSX files like I am today. Which is why I love the idea of writing just C# code since the entire back end is always in C# so we are super familiar with it. Just not sure how productive we will be writing the DOM rendering code in C# without some kind of declarative markup.

As much as I loved MVC code, writing the client side stuff in Javascript and jQuery is a nightmare while React is a breath of fresh air. So I am ready to ditch MVC completely and go React and WebAPI for all our future code development, just have to find the right path forward.

I imagine you have a lot of experience now writing Bridge.React C# code and components. How has your experience been in writing the DOM code this way, and other developers you work with? Clearly this is something you have thought about also given your comments above that you have thought about ways to write JSX/TSX for rendering the DOM in the past?

ProductiveRage commented 5 years ago

If you have the opportunity then I would suggest a small project that you write in "pure" C#. Personally, I've found writing the components using the helper methods to be quite painless. It makes it easy for VS to present you with intellisense options for everything and this makes typing it in much less arduous. Personally, I feel that even if there is some overhead to writing the components in a C# style (rather than declarative JSX/TSX), this is actually a small part of the code base and components should largely be "dumb" the vast majority of the time. As such, you'll be spending much more time writing and designing and maintaining the rest of the client application and that is all a great fit for C#.

If you try this and you still miss the declarative syntax too much then I think that there may still be merit in trying to write the majority of the application in Bridge and then writing the components and a little of the app scaffolding in TypeScript. That way, you can use the bindings to your Bridge code that Bridge provides for you - this means that you don't have to manually maintain typings / bindings on both sides.

A couple more things for you to consider since you're evaluating TypeScript vs Bridge. What I really like about Bridge is that you get the full power of VS and solution-wide analysis via Roslyn - it's easy to write EsLint / TsLint rules for TypeScript but they can only apply to the current file (unless I've got this wrong). You also have the benefit that you can create a VS "Shared Project" that you reference from your Bridge application and your server-side application and then you can share API types and pass them back and forth (using Json.NET and the Bridge version of it). This means that there's a whole boundary that you can get type information for "for free" (if you had your application in TypeScript and your WebAPI side in C# then you need bindings / type information for those API types on the client side). In TypeScript's favour, compiling TypeScript to JavaScript is much faster than Bridge - we have a large project that is taking thirty seconds to build and that's a drag. Another TypeScript benefit is that it very stable in terms of its reliability from one version to another - although TypeScript is moving fast and adding new features, it very rarely breaks things whereas Bridge does break things between versions much often than I'd like (for example, that meta data bug you raised - code built in 17.6.0 can't read in the meta data from code build in earlier versions and it's only a minor version change where the break happened, it would be more understandable if 17.6.0 required code that was built with 16.0.0 to be recompiled).

kendallb commented 5 years ago

Our current project using React is still quite small and I have ported one small component to Bridge already but can’t yet see an easy way to use it from the existing JavaScript. Probably have to go all in and try it, but it is a small project still so probably only 2-3 days of coding to port it all. And you are probably right about spending more time doing logic coding and a lot less building the UI elements.

Stability is important to me which is one reason I am considering Typescript, but it’s different enough that it will be a learning curve for our whole ream. With C# we can hit the ground running so that’s pretty big. And since bridge and the tools are open source fixing issues ourselves is possible if we need to.

And now that you mention it sharing type definitions with the C# REST apis is really sweet. Is there an example somewhere showing how to build a shared project? We already have shared code for our windows and android clients that talk to the WebAPI code, but I built those by building libraries specific to each environment that link to the same files. Maybe I need to move that code to it’s own assembly that is compiled to some kind of potable format like .net standard?

ProductiveRage commented 5 years ago

I must admit that it's been a long time since I've experimented with calling Bridge code from JavaScript (or TypeScript) but there's a page on the Bridge.NET site that makes it look very straight forward: Calling Your Code

I'm afraid that I don't have any public examples demonstrating using shared API types but it sounds a little bit similar to what you're doing with Android and Windows. It sounds like what you have might look a bit like this arrangement:

Project 1: WebAPI (this includes API request / response classes as well as the actual WebAPI controller code) Project 2: Windows Client (this references some of the WebAPI request / response classes as linked files) Project 3: Android Client (this also references those WebAPI request / response classes as linked files)

A Visual Studio Shared Project takes the concept of linked files one step further. You put files into a Shared Project (a special kind of project in Visual Studio) that you want other projects to share. The Shared Project itself doesn't actually build to any kind of portable format, it just allows other projects to reference that Shared Project and, in effect, have all of its files added as linked files and built into the consuming project.

So I have a solution that looks like a bit like this:

Project 1: API Types Shared Project (contains API request and response classes) Project 2: Server API (this references the API Types Shared Project and so produces a binary that includes all of those types) Project 3: Bridge Client (this also references the API Types Shared Project and so those types are available to Bridge code)

I use the standard Json.NET NuGet package in Project 2 (the C# Server) and I use the Bridge version of it in Project 3 (the Bridge app) - the Bridge version of the package does not support the full functionality of the .NET version but it has enough for many use cases (see [Bridge.Newtonsoft.Json])(https://github.com/bridgedotnet/Bridge.Newtonsoft.Json)).

kendallb commented 5 years ago

Ok thanks for the information, sounds similar to what we do already but I will check out shared projects.

I have an idea about how to share components. If we had a Typescript project that generated a single bundle file with all the components in it along with the types file (using webpack), and then made the Bridge.net project dependent on the Typescript one, wouldn't it be possible for the Bridge.net project to import the generated Typescript types at build time when it compiles the C# code? I am not that familiar with how Typescript types can be used in a Bridge.net project, but I am sure the process could be automated at build time so the steps would be:

  1. Webpack runs on the Typescript project to bundle all the component files (they could even be stored in the same directory as the C# files and referenced in that project, just not compiled).
  2. Generated .js and .d.ts files are processed in such a way Bridge.net can import them
  3. Bridge.net builds and references the .d.ts types so it can compile code that will call those
  4. Bridge.net includes the Typescript compiled .js file as a resource during it's build process and puts them into the directories for the ASP.NET web site (ie: the Scripts directory or something).
  5. All generated Javascript is then copied and output by Bridge.net into our normal ASP.NET web site project

Does this make sense and seem feasible? Maybe what I should do is start with your sample project for Bridge.React in Git, and see what it would take to convert it over to compile this way using Typescript for the TSX layout files as pure components.

It would also be nice if we could somehow make sure Bridge.React knows those are pure components so it can do the normal optimization stuff you do for PureComponent classes, to improve render times?

ProductiveRage commented 5 years ago

Bridge can't import TypeScript definitions.

The Bridge Team have created a project that reads definitions from Definitely Typed and emits Bridge bindings from them but that software is closed source.

The TypeScript type system has things that the C# type can't (or can't easily, in some cases) represent and so this is not a trivial process.

kendallb commented 5 years ago

Ahh, I see. So there is no easy way to consume existing Typescript into a Bridge.net project without building manual bindings? So that direction won't work.

I suppose the only other option would be to just write the components in JSX, and maybe build some kind of T4 template to generate a typed binding to the regular non-typed Javascript so the Bridge.net code knows how to call over to it and not worry about the types at all, as long as the C# code knows what types it is sending to the Javascript.

ProductiveRage commented 5 years ago

I still think that, if you want to try out TSX components, that you would be better writing a library in Bridge that contains 99% of the application logic and then referencing the resulting JS and its TypeScript definition from a TypeScript project, which primarily exists to define TSX components in but also has a tiny bit of scaffolding code to connect the Bridge application to the components.

kendallb commented 5 years ago

Yeah OK, so going the other direction. I didn't realize Bridge.net can output Typescript type definitions, so if that is the case then yeah it would make sense to do it that way so I will think about how to try that.

kendallb commented 5 years ago

Ok I am still a bit lost on how we would be able to use the Typescript pure components to do the rendering? Clearly compiling the C# code to Javascript and emitting the Typescript type definitions for it would allow Typescript to call into the C# code, but how would the C# code be able to use a Typescript component? Aren't we back at square one again? We would still need to write a C# wrapper for the Typescript component that references it as an external component?

ProductiveRage commented 5 years ago

So I suggested that:

you would be better writing a library in Bridge that contains 99% of the application logic and then referencing the resulting JS and its TypeScript definition from a TypeScript project, which primarily exists to define TSX components in but also has a tiny bit of scaffolding code to connect the Bridge application to the components.

This would mean that you do all rendering of components from within TypeScript code. That allows you to use TSX (and to not require Bridge wrappers for your TypeScript components).

Your application logic (your state-handling, API-calling, etc..) would be written in Bridge. You would reference the Bridge code from your TypeScript project. You will have some application startup ("scaffolding") code in TypeScript that gets a reference to whatever state-tracking references you need from the Bridge code and you would pass them into the top-level TSX container components as props (this should be easy because you get auto-generated TypeScript definitions for your Bridge application logic code).

kendallb commented 5 years ago

Ok, but how would the Bridge components do rendering, since at some point they need to implement the Render function? Since they won't really know how to call over to the TSX files without writing a C# wrapper for each one, would the bridge components be implemented to simply render the TSX components are children, or HOC's or something?

I am stuck trying to figure out how the C# Render() function is going to be able to somehow connect with the TSX components without writing some kind of wrapper for the TSX component?

ProductiveRage commented 5 years ago

Can you not write all of your components using TSX, since that's what you're more comfortable with?

And then write the real "meat" of the application in Bridge; the state-handling and the API interactions and all of the complicated stuff? If your components are all fairly "dumb" then you shouldn't have much to worry writing in TypeScript (relative to the size of the entire client application).

If you do have some components that seem to need some complicated logic in them then you could still write this code in supporting Bridge classes because calling from TypeScript to Bridge is easy.

If you don't have ANY Bridge components (because they're ALL TypeScript) then you don't have to have ANY C# Render methods and so you don't need any C# / Bridge wrapper classes.

This is what I was saying earlier, as per:

This would mean that you do all rendering of components from within TypeScript code. That allows you to use TSX (and to not require Bridge wrappers for your TypeScript components).

kendallb commented 5 years ago

Ahh, I see what you are saying yes. TSX code would call over to C# code to do all the real heavy lifting.

At this point I think I am just going to try going the pure C# route and see how the code looks and feels when I am done. Maybe after using it for a while it will feel more natural to write the render implementation in code rather than a declarative markup.