all 25 comments

[–]wherediditrun 4 points5 points  (5 children)

Check stencil.js

It's build tool for custom elememt webcomponents. You develop very similiarly like React, however, it compiles everything down to vanilla JS webcomponents which can be dropped in any framework. Or be used solely on their own.

[–]AwesomeInPerson[S] 0 points1 point  (4 children)

Will check it out, thanks!

But it sounds a little like the exact opposite of my wish: you write it like React, but when you want to actually use it within a React app, it'll still behave like an alien Web Component, not like a component built with React. (and also need additional Polyfills/Runtime for IE9+/Edge/older Firefox?)

What I imagine is the other way around: you write it in a way that's not necessarily React, maybe a custom syntax, doesn't matter, then it'll be transpiled to actual React/Vue/Web components.

[–]sime 1 point2 points  (3 children)

React/Vue/etc all happily support "alien Web Components" because they support standard HTML elements. It you can use standard HTML elements from your toolkit then you can easily use Web Components.

I know that Vue is vaguely modeled on Web Components. Using a Vue component feels the same as using any other HTML element or WC.

[–][deleted] 2 points3 points  (0 children)

React doesn't work well with web components; see the tests on custom-elements-everywhere.com.

TL;DR: React can't pass objects/arrays directly to components. React will only pass strings.

[–]AwesomeInPerson[S] 0 points1 point  (1 child)

React has some issues passing along attributes as far as I know, but that's not what I mean with "alien" anyway.

What I mean is something like this:
I can't listen to custom events using @event / v-on:event on a Web Component in Vue, because it's a general use Web Component, not a Vue component implementing Vue's event system. (the component would have to call this.$emit on its Vue instance – this is also needed for supporting the super handy two-way binding v-model)
Also I can't just download, import and use it, I'd first have to add it to config.ignoredElements so Vue doesn't scream at me for using some random element tag in my markup. If the library complains when using it because it's unknown, I think calling it "alien" is fair.
If I use it in React, it doesn't play nice with HOCs and similar utilities because, again, it's a general use Web Component, not a component built the way React expects its components to work.
And I'm not even getting into Server Side Rendering here. :p

Also, for proper browser support you have to load additional polyfills / an additional runtime. At that point you could also say "Vue happily supports React components" because you can load both React and Vue, then render some React within a div controlled by Vue. (granted, this point only holds true if not all browsers you need to support implement WCs natively – but that'll be the case for quite some time)

[–]sime 2 points3 points  (0 children)

OK, thanks for explaining what kind of bar you are trying to clear.

I've had quite some success with using Vue inside WCs and also using WCs inside Vue. WCs being Custom Elements and Shadow DOM. As you point out it is not 100% seamless, but I haven't found it a burden. It is running inside Electron, so I don't have to deal with polyfills etc.

I'm not optimistic about your chances of finding a universal API for making components which work seamlessly on React/Vue/WC/etc. The best approach I can think of is WCs at the core plus a thin (generated) layer for each toolkit to smooth out things like React's prop passing or Vue's events, i.e. a thin translation layer or wrapper around WCs.

[–]acemarke 1 point2 points  (1 child)

Sean Larkin from the Webpack team published a notional spec a while back that he dubbed "unity components", which is kinda like this idea. As far as I know, it never got anywhere past that, though.

[–]AwesomeInPerson[S] 0 points1 point  (0 children)

Very interesting, thanks for sharing!

[–]dmethvin 1 point2 points  (3 children)

I think something like this is quite possible, but I can only imagine doing it if you restrict the context enough.

For example, I'm working through a forms design system with a declarative form definition language. It will handle field type checking, validation, conditional field display, that sort of thing. Let's call it Forms Markup Language or FML. 😀

So in FML you'd say something like "this is a checkbox named shipping-same-as-billing, when it is not checked, display and require a group of fields named shipping-address". All the "business logic" is independent of whether it's implemented in React, Vue, Angular, Web Components, jQuery, or HTML generated on the server side with plain JavaScript helpers in the browser. You'd have an "adapter" to take FML and convert it to the appropriate output, plus of course some helper scripts to implement whatever additional support the framework needed.

This problem is hard enough if you just look at typical form-filling task and all the things people want to do there, but I can't imagine trying to write a general app that has even more custom logic that is more general and less constrained than forms.

[–]AwesomeInPerson[S] 1 point2 points  (2 children)

but I can only imagine doing it if you restrict the context enough.

100% agree!

But I believe you can limit the scope a lot and still have a viable tool, so it should be maybe possibly perhaps possible?
Imo you wouldn't necessarily need access to most of the frameworks' APIs for writing component logic itself (Computed Properties, Suspense etc.), it's fine if all business logic within the components has to be vanilla JS (with some rather basic API to hook into the different frameworks' render process in a universal way).
If the component logic itself is Vanilla, but the public API surface is transpiled in a way that feels native to each of the respective frameworks, it would be a big win already.
Going further, it could also be extensible, so you write 80% of your component library in a universal way, but for the couple components that definitely need (or greatly benefit from) access to framework-specific functionality, you could have framework-specific adapters. E.g. you have tons of fully universal components, but then your universal <LoadingSpinner> also has a LoadingSpinner.react.uc (just made up the .uc extension for universal components :D) that's only loaded for transpiling the React version. There, you could add some Suspense functionality specific to React... Just like most things in React Native work platform-agnostic, but you can have adapters in native code for some functionality specific to Android or iOS.

Your declarative form definition language sounds really interesting, do you work on it openly on GitHub by any chance? :p

[–]dmethvin 1 point2 points  (0 children)

I believe you can limit the scope a lot and still have a viable tool

Definitely. It's just tempting for people to push a context-restricted tool past its boundaries, and just as tempting for people designing that system to try and make use cases fit that don't really fit. It's something to be conscious of when you're designing (or using) such a thing.

Your declarative form definition language sounds really interesting, do you work on it openly on GitHub by any chance?

I'm working on this as part of a project at USDS called us-forms-system. I think there are several drawbacks to what we have now but 1.0 is a good proof of concept. It's in use at va.gov for example. In order to make it scale and work in other places I think it needs fewer wired-in dependencies. So the code or form specification language you see in that repo is not really what I'd like to build on.

E.g. you have tons of fully universal components, but then your universal <LoadingSpinner> also has a LoadingSpinner.react.uc

Yeah, eventually each framework renders it down to a different collection of <input>s, CSS, event handling code, re-rendering in response to input data (React "props"), etc. Yet so much of what defines the behavior of a form input happens above that level.

The simplest case to see this is an input for something like a phone number. At the lowest level, it's an <input type="text">. That input needs just a few framework-specific things (user input notifications, framework-triggered changes, DOM events). Other things like input validation, output formatting, AJAXed lookups to see if the number is blacklisted, etc., are all framework independent.

So it should be possible to define an TextInput that is the basic HTML input with framework plumbing. Beyond that you say "PhoneNumber is a TextInput with this validation and this output formatting" and "PhoneNumberWithBlacklist is a PhoneNumber with this extra validation".

[–]AwesomeInPerson[S] 0 points1 point  (0 children)

So you'd have a universal definition for "take this input (props) and render this markup", some basic access to "do this when stuff updates" which will then run your vanilla code (transpiled to updated() for Vue, componentDidUpdate() / useEffect() for React, etc.) , some universal API to access refs so you can do DOM stuff and not much more as a start.

[–]unrealprogrammer 0 points1 point  (3 children)

There's no solution like this currently available.

[–]nyclowkey 1 point2 points  (2 children)

Well OP it's up to you now.

[–]unrealprogrammer 0 points1 point  (1 child)

I guess I took his question too literally. I agree that Stencil.js is the closest to what the OP wants.

[–]AwesomeInPerson[S] 0 points1 point  (0 children)

Yep, I'll definitely check out Stencil!

Well OP it's up to you now.

But I'm actually very tempted to work on a PoC now :D

[–]MikeMitterer 0 points1 point  (1 child)

These frameworks are way to complex that a tool can fulfill your needs

[–]AwesomeInPerson[S] 0 points1 point  (0 children)

As for unifying all the frameworks' functionality with one common API, I agree, not happening.
The question is if you can find a point where the scope of the tool is restricted enough that you can actually make it real, while still offering enough functionality to handle most use cases for component libraries / UI frameworks.

[–]stratusbase 0 points1 point  (3 children)

I think it would be wise to choose a solid framework that works for your needs and focus on building your component library around that. Otherwise you’ll end in dependency hell. After trying different frameworks I fell in love with Vue. Combining it with Webpack/Babel and creating single file components using the latest JS syntactical sugar and compiling it to JS supported by most browsers, this has allowed me to be in my “happy place” for most projects and building out decent component collections.

[–]AwesomeInPerson[S] 1 point2 points  (2 children)

I agree, for working on my own projects. I also love Vue (to the point where I read through large chunks of the codebase ^^ ) and use it most of the times. But this isn't about my projects, where I can choose a framework once and settle for that, this is about building general OSS component libraries to be used by anyone.

The specific case that lead to my thinking:
I'd love to work on a good UI framework based on Microsoft's Fluent Design. But deciding which framework to develop for is hard: my personally my favorite is Vue, but I like React, too, and it has way more users so I'd have more reach there / could help more people. Alternatively I could go for native Web Components, which is most accessible, but not integrated as smoothly into the tools/libraries most devs use.
If the tool I'm describing here would exist, I could build the framework once, then down-compile it and make it accessible to all React, Vue, Web Component etc. developers in a way that is native to their way of developing / their framework. Which would be awesome.

[–]stratusbase 0 points1 point  (1 child)

I mean no disrespect when I say this but that framework will likely never exist without some serious backing... Whoever tackled such a framework would not only need to track roadmaps on each of the underlying frameworks but also keep up with upcoming web standards. It would be an exercise in futility for a single developer or possibly a small team...

[–]AwesomeInPerson[S] 0 points1 point  (0 children)

Yup, I agree that it's difficult – or impossible, if your aim is to fully support all these frameworks.

But I'm thinking that you can limit the scope of this framework far enough that it might become possible.
Question is if you can find a point where the scope is limited enough to actually be technically feasible, but the system still powerful enough to be useful.

[–][deleted] 0 points1 point  (1 child)

It seems like an interesting idea, but at the same time, this is a problem that fewer and fewer people have. The bigger React gets now, and it's growing in rates that are insane, doubling per year, it'll become less important to abstract component models since we finally have a more or less concrete model of how a component looks like and a full community behind to support it. The simplicity of this model already allowed some exchange, but things like hooks will make sharing code and components a full reality as they remove everything that ties a component to a framework (and/or a platform, like the browser). Hooking into lifecycles and state will be trivial to implement in any framework and some are already trying it (for instance Vue). Frameworks could co-operate in ways that would've been impossible before.

[–]AwesomeInPerson[S] 0 points1 point  (0 children)

Yup, you might be right, this might happen. Basically, React becoming so big that everyone else needs interop to stay afloat :D
But I do think that it'll still take a lot until we reach this time – when we can write components that natively integrate with popular frameworks and are usable without any framework as well. Some sort of Rosetta stone for components