Exploring Cross-Framework Development

Giorgi Cheishvili
Fundamental Library
8 min readOct 19, 2022
Picture by Tudor Baciu in Unsplash.

In today’s development, we as developers of UI kits face a common problem: how to allow as many platforms and frameworks to use our UI kits without losing any of the features and advantages that those frameworks and platforms have, efficiently. After all, those advantages were the main reason for people to choose their beloved frameworks.

We already have fundamental-styles, which is pure HTML+CSS implementation of Fiori and Horizon design guidelines, but what if we want to support them further in their development journey? It is neither an efficient, nor redundant-free way to support all different environments separately. So to understand how others are supporting multiple frameworks for their UI Kits, I dived in and explored the world of both open-source and commercial implementations of some UI kits. From all the explored libraries it was clear that today there are two main approaches used today:

  • Implementing different frameworks separately
  • Leveraging Web Components standard
Screenshot from Kendo-UI website

Kendo UI is a commercial UI kit for development. They support Angular, React, Vue and jQuery (yes, jQuery). They develop for every platform separately, if they decided to move on with this approach and add an additional framework, they will implement it from scratch and I think you already got a problem with this approach.

Pros:

  • Full support of every feature that those frameworks give. They can use any cool new thing those frameworks provide.

Cons:

  • Effort is enormous
  • Development teams are split mostly
  • Adding or modifying features to the components involves many steps across many source codes
  • High redundancy
Screenshot from Ionic website

So because of these cons, Ionic team decided to go on a different path. They decided to create their own framework for dealing with the need of supporting multiple environments. They called it Stencil.
What Stencil does is a very simple thing from the surface. They have their own JSX typings, their own format for declaring components, and the way of writing them. They have full control over the syntax which is provided and when you want to export your Stencil components, they just parse your code and compile it to the Web components. Then they just create wrappers for target frameworks. For the users of those target frameworks from the surface, nothing is “wrong” or unusual with those components, but from the inside, those components are not native to their frameworks, they are just shells, using Web Components from the inside.

Pros:

  • Little to no redundancy
  • Single source, so that you can modify for many targets at once fairly quickly

Cons:

  • You have to write components for Stencil syntax exclusively
  • Web components are not that great if you want to use something like Qwik. If you apply the same method to creating Qwik Components, you’ll lose the whole point of having Qwik in the first place. So just a short takeaway is that it is not always perfectly leverage the target framework’s features.

There is also another framework compiling to Web components. It is called Lit, formerly known as PolymerJS. Lit is a framework, which creates the web components, which can be used directly in most frameworks, but here’s the thing with Web components in general. Intellisense is problematic.
TL;DR is that with Web components autocompletion could be achieved by either exporting the public API of the elements into two separate JSON schemas. One for Visual studio code and the second for IntelliJ platform IDEs. Then use them in two different ways, for VS code, use that JSON in settings, and for IntelliJ, you should create PR here.
The second way is to create TS Language service(?), I have not dived into it yet.

For me, it is obvious that it is not perfect. Nowhere near frameworks’ integrations with IDEs.

Screenshot from Mitosis Github page

Few (very)smart people gathered and thought about it and concluded that it is best to compile your code for target frameworks.

How might such a thing work? It is simple(kinda)

When you write code, you can get AST(Abstract Syntax Tree) of that file, which is very hard to work on, even though there are numerous helpers for handling it, for ordinary developers, it is nightmare. But what if you transform that AST into a fairly easily readable JSON schema and then feed that schema to the adapters, which will generate code for the target syntax? It is only concerned with one component at a time and can, at least theoretically, provide a guarantee, that one file will be completely compatible with the target. The goal is fairly simple from the surface: being able to write in one universal syntax, get a workable representation of that code, and then have per-target adapters for generating code.

The best part is that you are not forced to use only one syntax, you can use either provided parsers from Mitosis, namely parser for mitosis syntax, to mitosis.json, or Builder.io format and that can be extended by users.

I tried using mitosis.js and NX workspace, to generate simple Fiori components from mitosis.js format to Angular and React, using fundamental-styles. I loved the idea. Implementation needs polishing, but the idea is solid(by the way, they also provide a SolidJS generator).

Here is the result of it: https://github.com/g-cheishvili/fundamental-mitosis

So let me walk through what I have done

Mitosis has plugins, it gives you full control of how it treats source and output files

How mitosis plugin is organized

So judging from this we can write any additional feature that we want to have to fill in the missing features’ gap of the provided generators without getting our hands dirty.

In the NX workspace, I created an NX plugin called mitosis-tools and added an executor compile. This executor just takes information provided by the user and calls the generator with it. The generator goes on and calls mitosis for the files, which are listed in the project configuration of the library, then goes on and generates the target framework’s specific files using EJS templates. Examples include ng-package.json for Angular, entry point public_api.ts, NgModule for composite components, and forms support.
Also, the generator copies over asset files, which are listed in the project as such.

As a result, I can type:

nx mitosis-button:compile --targetFrameworks=angular

and after a second I have a fully functional Angular component ready, baked for me to serve to my users.

Or I could type:

nx mitosis-button:compile --targetFrameworks=react

and I have React compatible components.

Is not this awesome? But let’s talk about the future of such an approach to development by taking an example from an imaginary(probably based on real events) story from a company, which has its own design guidelines, creates UI kits, and allows third-party developers to use those components either as an open source or sells licenses commercially. Let's say that company supports 2 major frameworks/libraries: Angular and React. At this point, let's say that they achieved that support via implementing both frameworks separately and those implementations have common only the CSS part. Months or years went by and a third major framework/library appeared on landscape and now users of this library demand that their beloved UI kit is available for the new framework too.

What would this company do? They would probably start implementing the third library and inevitably add additional effort into supporting their UI kit across multiple libraries.

Let's suppose they have Web Components’ based UI kit and have 2 wrappers around it. Then they would create a third wrapper. We have two cases here: manual or automatic wrapping. If they wrap manually, the effort for doing such a thing might not be far away from the effort of writing things from scratch natively for that framework.

If they wrap automatically, then they would have to write scripts for automatic transpiling. It would work, and the effort might be somewhat one-time, but the issue is that that transpiler will be useless outside the context of that said UI kit and its team. And after all these efforts it will be just wrappers. I do not believe that wrappers can have all the available features.

Take a look at the Qwik framework. How do you imagine wrapping Web Element and not losing Qwik’s twists?

Even if you use tools like Stencil, you still have Web Component wrapped inside native components. Which, does its job, but is not ideal

Also, there is a case with framework updates. Native code that we write for frameworks is not disposable. They are the source, not the byproduct. So when something changes in framework specifics, some major feature is added or removed, we are left to modify everything that we have, in hopes of advancements. When you manage many frameworks and some of them get updated, you are stuck in this cycle of updating things…

Take for example React’s approach to writing components. Now, if you google any topic related to React, chances are that you’ll encounter hooks and functional components in most results.

In past, Stateful components were not bad, it was acceptable and you could perfectly do things with them. A few years ago they have decided that the future is a functional approach. Although the transformation was not that hard, it is still a manual effort. In an ideal world, they would just release an adapter update, which would be consumed by something like mitosis and your UI library would be updated to the latest standards with one compilation.

Back in the day, AngularJS was a hit, for its time, it was revolutionary! Time passed and better frameworks were created. Angular was created which was an entirely different framework and every library that was created for AngularJS became obsolete. With the mitosis-like framework, such problems can be avoided altogether. You would just compile to a newer version and you’re good to go.

Even though working with Mitosis for this moment(September of 2022) was not the best development experience, I believe that such an approach will be at some point in the future best way to go, once algorithms for transpilation are polished. Quality will also benefit once we arrive at that point, we could take other people’s expertise in certain frameworks and use them in our products.

In upper mentioned cases, if framework(s) like mitosis are popular, authors of other frameworks will just release generators for allowing users to switch to their frameworks painlessly and fully leverage all the pros that they offer. Update the generator and you can entirely modify your codebase on a previously unimaginable level.

Using new technology in frameworks would be a breeze. Wanna manage your state using Redux? Cool, configure your generator to use Redux, wanna use hooks? Configure. Want classes, instead of functions? Configure. No need for huge development processes, just an updated generator!

Take a look at my repository, here I can update one source file and automatically deploy changes to two entirely different libraries with one command! How cool is that?

--

--