Open skrat opened 7 years ago
Preact doesn't work with React-native.
Lack of stateful components would require changes in Reagent and would break many projects using Reagent.
But maybe it would be possible provide an option to use Preact.
First thing that could be done separate to Reagent, would be to create package for Preact: https://github.com/cljsjs/packages
With all the fast, lightweight competing JS frameworks today, what basis would you choose preact over something like mithril, vidom, or inferno? If there's to be a discussion on being able to optionally wrap another framework, should we first discuss a way to evaluate the best alternative?
Aside from the React Native point (which is correct and can be a reason to use React itself), the reason one might choose preact is because it maintains nearly perfect API compatibility with React. This is very different from switching to an arbitrary frontend framework, since it does not require any changes to your code.
Stateful components are definitely supported by the way, let me know if there's an article or something out there seeming to imply otherwise. Also preact works fine without ES6 or anything, it supports IE9+. Cheers!
Preact retains a large amount of compatibility with React, but only the stateless functional components and ES6 Classes interfaces.
I just skipped ES6 classes part as it is not something I'd be usually interested in.
As the API is nearly perfectly compatible it is possible that nothing needs to be changed in Reagent and users could just change the dependency. For this to be possible, someone should finish packaging Preact in Cljsjs: https://github.com/cljsjs/packages/pull/719
With the https://github.com/cljsjs/packages/pull/719#event-857134830 merged, should we try to do this and see what happens?
I'd be excited to see a demo of this if anyone gives it a go. Really neat stuff!
Just adding my two cents. I started working on a fork previously that used inferno and found converting from react to inferno to be very simple. However, I only used stateless components. Eventually I abandoned the fork due to lack of time and testing utilities for inferno. Of the virtual DOM libraries I have tried React is currently the only one that has its own testing utilities. I think rendering components to hiccup data structures would increase test-ability and allow an easier transition to other JS frameworks.
@KeeganMyers preact-jsx-chai is fairly capable for testing, though I'm not sure how these types of things work for ClojureScript. I am working on a way to use Enzyme with Preact, if that's what you were referring to (since React's TestUtilities option is deprecated).
I don't mean to imply that you should consider them equivalent though. Preact is an optimization for web, React is a reference implementation with all the necessary abstractions for multiplatform. I think the compelling thing for Reagent would just be a flag that would let people output preact if their use-case is a good fit for it.
Is someone working on this ? planing to hack on this over the weekend!
@piecyk I was planning to but having hard time finding time, go ahead.
@piecyk hack away! 😍
After quick look... just included preact
, preact-compat
, preact-render-to-string
we got
Ran 101 tests containing 2286 assertions.
11 failures, 2 errors.
and demo page is fully working :tada: out of box, awesome work @developit :clap:
I think i will create reagent.impl.provider
that will have refs to react / preact or better export functions to don't have refs to it in ohter places just hide it here, i think we don't need all stuff from preact-compat
etc...
I can fully understand the desire for something like this, but I don't know if I'd like to see it as part of Reagent, at least not initially. Reagent was built around React and has close integration with it. While it would be possible to add another layer of abstraction to let people choose between React and Preact, what will happen if/when React provides features that we want to use but Preact doesn't support? I fear that we would run into a lowest common denominator situation.
An alternative option could be a friendly fork of Reagent (perhaps even within the reagent-project
org) which swaps out React for Preact entirely. That project can then have room to breathe, experiment, and find what is the best way to write the code. If in (say) a year, there is continued interest in the project (and Preact is still being maintained(!)) then it could be possible to reintegrate Preagent with Reagent.
That option would also have the advantage of giving you a much faster feedback loop. It's quite likely a change like the one you are discussing will take a while for Dan to approve and review.
With all that being said, it sounds at the moment like this may be a pretty minimal change, so perhaps a separate project isn't necessary?
While it would be possible to add another layer of abstraction
It's not a layer of abstraction, it's a configuration variable + swappable backend for an API that Reagent is using
what will happen if/when React provides features that we want to use but Preact doesn't support
I don't think that's a valid reasoning. I think Reagent is built around using React's API, and if that API is implemented by different library, with different features (size) that users are after, then why not? Taking into account hypothetical future features of React is not a valid argument.
It is a very common pattern to have different backends and I don't see any downsides to it. The usual approach to provide some implementation specific is to expose the backend and let the users do anything they want to do with it.
@danielcompton you are so true! Also seeing how fiber
can change a lot in near future regarding how react internal will work bring more doubts about this... preact
on the other hand can win with some web worker integration
It looks like preact
did great job and out of box 99% is working the same, just poking with this looks like it will be so small change, IMO we could just provide 3 files with api contracts that will dispatch correct implementation regarding what people want to use
(ns reagent.impl.provider)
(ns reagent.impl.dom-provider)
(ns reagent.impl.dom-server-provider)
@skrat I'm not sure if what I was trying to communicate in my last comment came through very well, so I'll try again.
In principle, I'm not opposed to the idea of Reagent using Preact. It has some cool features and I like that it is small (although in comparison to the total compiled size of a normal CLJS app it's probably a wash). If Preact worked 100% out of the box with Reagent with no code changes required then I would have no issues with someone swapping out the React dependency for a Preact one and calling it a day. If there are only a few minor tweaks to Reagent required to pass the tests, then again I don't really have any issues with that. I suspect that even if you have 100% of the tests passing, there will still be issues, as Reagent was built around React, and may not have tests that would cover the difference in behaviour between React and Preact.
It looks like it may not be a pure lift and shift to support Preact. If that's the case then we run into a bigger issue: indirection. Reagent was written and built around the ideas and in the context of React. There are assumptions (probably tens or hundreds) built around React's API and possibly implementation details too.
Adding abstraction adds a large cost because you can no longer program against a concrete API and implementation, you now have to consider two. There are three numbers in computer science, 0, 1, and many. We would be moving from 1 to many, and that takes work.
An aside: recently at work, we were looking at moving a legacy system from only supporting dates in the past to also be able to support dates in the future. This should be straightforward right? We talked to the programmers responsible for it and they couldn't guarantee that it would work, nor whether supporting future dates would be easy or hard. In the building of that (or any) system, hundreds of simplifying assumptions are made around the context that the system is going to be built in.
It is a very common pattern to have different backends and I don't see any downsides to it.
I can't think of a single example of a system with multiple backends that didn't have any downsides to it, e.g. ORMs, HTML/CSS/JS, Java. There may be some, but they would be the exceptions that prove the rule. Everything has a cost, the question is whether there is a benefit that outweighs the cost. It is much harder to remove something from software than to add it, which is why we should be certain that the benefits outweigh the costs.
While Preact strives to be API-compatible with React, portions of the interface are intentionally not included. The most noteworthy of these is createClass() ... https://preactjs.com/guide/switching-to-preact#3-update-any-legacy-code
Reagent currently uses createClass. There are workaround options provided, but this is an example of some of the API differences between React and Preact which you need extra compatibility layers to support. Do we know if the compatibility layer works 100% correctly?
As a thought experiment, let's assume that Preact is in Reagent with some kind of compatibility shim. Preact already has several performance optimisations that people can take advantage of:
customizable update batching, optional async rendering, DOM recycling and optimized event handling via Linked State. - (from Preact homepage)
Wouldn't you want to be able to take advantage of those in your application? I certainly would. Now to do so, you may run into issues because the compatibility shim layer that was written was encoded around default assumptions of React, and they may not apply to Preact. Do we have to rework the shim layer, or lower level Reagent API stuff? Who is going to do that work? Who is going to review it and merge it?
Let's consider the reverse. Perhaps in some new React version, Facebook comes out with a new API which is faster or better suited to Reagent's style of rendering, so we want to switch to that. However that new model may not work with Preact. Again, we're in a bit of a pickle: Preact users want to be carried along with Reagent and get the benefits of new Reagent work, but it may not be easy or possible to support the new API for them. Now what?
Consider everyday development on Reagent. Reagent's source code is built around a very detailed understanding of React and is highly optimised. If Preact was supported too, then developers would probably need to gain an understanding of Preact too.
At the moment, Preact has one main contributor, it has been around for 1.5 years. React has many contributors. I'd estimate there are 100+ people with very deep knowledge of React. It's been around (in public form) for 3.5 years. In general, the JavaScript community does not have a reputation for long-term support of projects. What happens if development slows/stops on Preact and the compatibility layer isn't kept up to date? It is much harder to remove something than it is to add it. Who decides when/if to remove Preact from Reagent at a future date?
These are all hypotheticals, but I hope this demonstrates that the extra abstraction provided by supporting two VDOM layers doesn't come for free. At the very least, it consumes extra brain cycles when testing and developing Reagent, extra support and documentation costs from users wanting to use one or the other, as well as extra indirection when running and debugging apps using Reagent.
If you haven't already, I highly recommend reading "The Innovators Dilemma" by Clayton Christensen. One of the key points he makes in that book is the difference between integrated and modular products and when to develop each kind.
CHRISTENSEN: When the functionality of a product or service overshoots what customers can use, it changes the way companies have to compete. When the product isn’t yet good enough, the way you compete is by making better products. In order to make better products, the architecture of the product has to be interdependent and proprietary in character.
In the early years of the mainframe computer, for example, you could not have existed as an independent contract manufacturer of mainframe computers, because the way they were made depended upon the art that was employed in the design. The way you designed them depended upon the art that you would employ in manufacturing. There were no rules of design for manufacturing.
Similarly, you could not have existed as an independent maker of logic circuitry or operating systems or core memory because the design of those subsystems was interdependent. The reason for the interdependence was that the product wasn’t good enough. In every product generation, the engineers were compelled by competition to fit the pieces of the system together in a more efficient way to wring the maximum performance possible out of the technology that was available at the time. This meant that you had to do everything in order to do anything. When the way you compete is to make better products, there is a big competitive advantage to being integrated. ... In order to compete in that way, to be fast and flexible and responsive, the architecture of the product has to evolve toward modularity. Then, because the functionality is more than good enough, you can afford to have standard interfaces; you can trade off performance to get the advantages of speed and flexibility. These standard interfaces then enable independent providers of pieces of the system to thrive, and the industry comes to be dominated by a population of specialized firms rather than integrated companies.
I would argue that we are still very much at the point where the current VDOM libraries aren't good enough yet. They aren't yet ready to be commoditised, and the best option is to tightly integrate.
Someone can make a PR to Reagent to add support for Preact. It will probably take a while to get merged because it is a significant change. Once it is merged and released, there will probably need to be several rounds of revisions before it is ready to go. Because Reagent moves relatively slowly, this will take a while.
Reagent also has a large number of production users, so new releases need to be well tested and stable. Adding Preact to the mix is going to slow this down further.
Someone can make a fork of Reagent (let's say it's called Preagent). You can run wild experimenting with what is the best way to use Preact in Preagent, take advantage of all of the great features Preact has, and have a much faster turnaround time for releasing and using it. You will be able to work out what is the right API and integration points for Preact because you have room to experiment with it, without the weight and responsibility of bringing the rest of the Reagent users along with you.
At some point in the future, you could review merging Preagent back into Reagent, given all that you now know. You would also have the weight of evidence on your side where you can demonstrate the benefits of Preact and can show how many users want Preact. This would let you make a much better case for including Preact, give you what you want in the meantime, and likely provide a higher quality integration in the future.
Alternatively, you may decide that Preagent is better served going its own way and integrating more closely with Preact. This is also a good option.
The point I have been trying to drive through this post is that abstraction is not free. Over-abstraction is a common anti-pattern and it saps productivity. I had a friend who recently left a Clojure job and started a Java one. He quipped to me about how he'd forgotten what it was like to trace code through five layers of abstraction to get to the concrete implementation. As programmers, we're trained to solve problems by adding layers of abstraction, but that isn't always the best way to solve the problem.
Lastly, this isn't a personal attack on you or your ideas. I'm all for innovation in ClojureScript webapps and I think that it is worth investigating Preact and how it could work in the ClojureScript ecosystem 😄 . I'm not against Preact. I would consider using it if there was a measurable benefit. I'm just suggesting that the best way to go about this is probably not to integrate it into Reagent as the first step. ✌️
Update 2018-11-23: I published this at https://danielcompton.net/2018/11/23/on-abstraction.
Well said @danielcompton :)
+1 regarding idea of "preagent" (i.e., reagent fork focused on preact)
Someone should experiment if there is even need for any changes in Reagent, in theory Preact (and for that matter Inferno) both provide the same API as React so Reagent doesn't even need to care which one is used. Though this will require using https://git.io/preact-compat or https://github.com/trueadm/inferno/tree/master/packages/inferno-compat, to provide React and ReactDOM objects.
Just wanted to drop a note to say that was a really excellent post @danielcompton. Should be a blog post! 😉
I totally agree regarding abstraction, even if it is something as theoretically simple as an alias - it's still a means of indirection. It's funny to see the case for reduced abstraction being made here, since that was essentially the original experiment that turned into Preact.
I think a "Preagent" (I should apologise for starting that naming trend) project seems good - it'd let the people interested in experimenting with Preact as a renderer iterate, without needing to work within the release cycle of Reagent itself.
I packaged react-compat (and proptypes) for cljsjs and used exclusions to replace React with Preact: https://github.com/Deraen/problems/tree/reagent-preact
At least a simple demo is working without any changes to Reagent. With master at least. https://github.com/reagent-project/reagent/pull/276 is required because other Reagent will try to load react-dom-server which is I did not package yet, but seems like it also available.
Testing this requires locally installing two packages from https://github.com/cljsjs/packages/pull/875 for now. Will finish this later today.
@developit what React version is Preact-compat targetting? preactCompat.DOM
doesn't seem to contain render
, findDOMNode
or unmountComponentAtNode
which where moved from React to ReactDOM in React 15.
I worked around this with a small wrapper: https://github.com/cljsjs/packages/blob/bcb88731505f2168500446b1be24980fa3c24c45/preact-compat/resources/cljsjs/preact-compat/common/react.inc.js
Btw. demo project 286KB with React and 173KB with Preact. Non gzipped.
preact-compat
is both the React and ReactDOM module exports in one package (no .DOM needed). It targets 0.14.x and 15.x.
You can simplify your file by doing:
window.React = window.ReactDOM = preactCompat;
Thanks. Just realized that myself.
Nice. Would be curious to see the gzipped difference too (I use npm.im/gzip-size-cli to check).
77.8KB with React and 42.8KB with Preact.
Thanks, good to know!
@Deraen Tried your preact branch and while it works for the simplest case, adding a nested component to the view gives me Using native React classes directly in Hiccup forms is not supported. Use create-element or adapt-react-class instead: ReagentInput
Can you reproduce? It seems to happen with something as simple as:
(defn view1 []
[:input])
(defn view2 []
[:div
[view1]])
I can't read this code to save my life, but the error is thrown here. Seems like view1
in your example is pre-transformed to a class instead of a function? Not sure where that process happens.
It might be an interesting idea to have a pluggable backend API in Reagent. If there was a protocol, then it could be implemented by any implementation that provides a VDOM, and open a possibility of having a native ClojureScript implementation.
I'm open to protocol or something similar. It means we'd have to break things by removing requires on React and user would need to require namespace providing React implementation and probably provide that to render
as option. Next version will probably break things anyway (:require
changes coming in next Cljs release) so maybe it makes sense to do this at the same time.
Providing adapter to only render
is probably not enough as some things that need to call React happen outside of render loop. If we just have global adapter value and function to change that, it could be possible to implement this without breakage.
I'm still of the opinion that abstracting this out in Reagent at this stage is too early. Making it pluggable means that we need to find the right abstraction. This is difficult to do without implementation experience, and once the protocol is created it will be hard to change. I would suggest forking Reagent to use a different rendering backend, then making a PR to merge it back in after you've learnt the implementation lessons and have a good idea of what a good protocol for this would look like.
OTOH I haven't thought too much about what a protocol would look like here, so maybe the protocol would be obvious and simple?
I think doing a proof of concept fork would be a good idea. I think creating a protocol will result in cleaner code as well, as it would require untangling anything that's React specific from Reagent.
It would be good to work back from the API that Reagent exposes to the user and inform the protocol based on that. The good part is that Ragent API is fairly small, so I don't expect the protocol to be terribly complex, but I could be wrong here. :)
If nothing else, there would be value in the exercise just to figure out what constitutes core functionality in Reagent.
I stumbled upon this today: https://medium.com/@raulk/if-youre-a-startup-you-should-not-use-react-reflecting-on-the-bsd-patents-license-b049d4a67dd2
I think the licensing issue is a huge deal.
And there are contrary opinions on the patent clause which say the fuss is much ado about nothing.
https://medium.com/@dwalsh.sdlr/react-facebook-and-the-revokable-patent-license-why-its-a-paper-25c40c50b562
I'm not a lawyer, so this is hard for me to judge legally but, empirically, React has been out for 3.5 years, and I've not heard of a single startup getting sued.
But if you are sufficiently worried and motivated, I think the previous advice still stands: you should fork Reagent and create a proof of concept based on Preact. I'm fairly sure the current maintainers are not about to change Reagent itself in this regard.
I agree that this is unlikely to be an issue for most people. However, the fact that Facebook can arbitrarily decide to sue you is a valid concern. The clause is intentionally vague, and Apache Foundation seems to think this is reason enough to black list React from their projects.
I also think there are other advantages aside from the license issue in abstracting the React layer. If somebody is motivated to explore decoupling React, it should be done in a way that's backend agnostic. As @Deraen notes, the team would be open to having a protocol. This would also help isolate what constitutes core functionality of Reagent.
Facebook will sue you when it sees your app (using React) as a threat to their business.
Facebook just relicensed React to MIT: https://code.facebook.com/posts/300798627056246/relicensing-react-jest-flow-and-immutable-js/
I think this allays any fears over the React license. I do think that decoupling from React might be worth exploring though, so I'd keep this open in case somebody wants to look into it.
I updated @Deraen's project to use the latest cljs and reagent: https://github.com/tomconnors/problems/tree/reagent-preact. For anyone who just wants a smaller build size, this seems like a pretty good option right now. All that was required to get this simple project to run was to provide the lein :exclusions
and the :foreign-libs
and :externs
options to the compiler. I'd hoped to use the :npm-deps
option, but that doesn't appear to work because we need to use the "preact" package to produce the "react" namespaces.
Is anyone still attempting this?
What about using the preact/compat alias package? npm install react@npm:@preact/compat react-dom@npm:@preact/compat
Are we considering this?
What would be our road-map, limitations, etc.?
the licensing isn't all there is to Preact, it comes with a good deal of better features than React, such as Signals and a lot less bloat
I would like to open a discussion about this. Preact is much simpler, with easier licensing, and way smaller in size, with mostly compatible API.
https://preactjs.com/
As of the ES6 API, it should be possible to leverage Closure Compiler for cross-compiling to ES5.