preactjs / preact

⚛️ Fast 3kB React alternative with the same modern API. Components & Virtual DOM.
https://preactjs.com
MIT License
36.9k stars 1.95k forks source link

The road to Preact 11 #2621

Open marvinhagemeister opened 4 years ago

marvinhagemeister commented 4 years ago

With Preact X being a great success there The 10.x release line was all about increasing ecosystem compatibility and the initiative was a complete success. Compared to Preact 8.x there are many more third party libraries that work out of the box with Preact, we saw the addition of Fragments, hooks, a revamped devtools extension, prefresh for native HMR support and much more.

In summary, we learned a ton the past year whilst maintaining Preact X and we want to push Preact even further. So the next question for us is: Where do we go from here? What should a successor look like?

This is a collection of some of our thoughts, but is in no means a concrete feature list for Preact 11. Please keep that in mind.

Size reductions

Our small (smol?) size has always been one of our strong points and the thing I'm personally the most proud of. With an ever growing feature-set we're currently very close to the 4kB mark and would like to bring that number down again. Whilst we may be able to shave of a couple bytes here and there, I think we won't have substantial changes on that front without thinking critical of each feature.

Only include what's actually used

The most common talking point is that many projects (including preact-devtools) don't use a single class component. The natural question that arises here is why we need to still pay for the cost the class API regardless. Ideally only the features that are actually used should be shipped to users.

The same is true for preact/compat which will always include side-effects even though a user may only needs to import to the Portal component for example. There have been some attempts to do that in the past, but to me it seems like some changes in core could make that easier.

The overall motto should be: Only include what's used.

Move IS_NON_DIMENSIONAL to compat

Whilst it sounded cute on paper it has turned into a growing list of properties (see #2608 ) which is something we never wanted to have in core. The reason we kept it over from the 8.x line in core, was that it allowed us to keep old codepen demos working. This was especially useful at a time where we weren't officially committing to Preact X and we were more just playing around with various approaches.

I'd even go so far as to consider it harmful as I've seen it confuse new developers first-hand when switching between writing CSS and declaring inline styles. It's a feature we cannot remove entirely because of React, but moving over to compat should be fine.

Reconciler performance

Whilst the original plan for X was to break from using the DOM for traversal and only use the vnode tree for that, we ended up in the a little bit awkward middle ground. Most operations walk the vnode tree, but there are some remaining ones that still rely on the DOM itself. This is made more difficult by the existence of Fragments.

For our next generation we should cut ties with our past and completely base it on the vnode tree instead. The newly added statistic metrics reveal some great places to look for improvements. Some of the ideas that are currently floating around:

The effect queue idea is in particular interesting as it would the browser to batch all paint jobs and process them at once instead of having those intertwined with running JS. Heck we could make a lot of the DOM pointer code easier by applying those changes right-to-left instead of left-to-right.

What's more is that having an effect queue would open up the possibility for custom renderers. It's a long shot and not something we'll focus on in the near term, but we would at least have the possibility to do so nonetheless.

Boosting hydration

We're already in a good position when it comes to hydration performance but we have a lot of ideas what we can do to make it even faster. Any optimizations we'll do on our reconciler will directly benefit hydration, so there is a very close connection between the two.

Besides reconciler performance, there is a need to re-evaluate how we can best boot up from SSR'ed content. Due to us not joining adjacent text nodes anymore we have a mismatch during hydration. SSR'ed HTML will always create a single text node, even if it was created from multiple ones.

// Element with two Text nodes
<div>Hello {name}</div>

An alternative to joining adjacent text nodes is to insert HTML comments as markers in-between them. Not sure which approach is ultimately easier, although my gut tells me that the latter can easily become complex.

Remove the need for forwardRef

The introduction of the forwardRef component is mostly a workaround for not keeping ref in props. If we keep it in there we can make all forwardRef components redundant:

// Current way
const Foo = forwardRef((props, ref) => <div ref={ref}>{props.children}</div>);

// Proposal
const Foo = props => <div ref={props.ref}>{props.children}</div>;

There is a downside to that though in that there may be custom runtime checks in third-party libraries that explicitly check for additional properties in the props object. We ran into some of those in the past if my memory serves me well, and I'm secretly hoping that the increasing adoption of TypeScript has improved the situation compared to a few years ago.

Mark root nodes as roots

Both the devtools and Portal component would benefit from having a way to place sub-trees into existing ones. Currently no tree knows about the others which leads to some weird edge cases with Portals. There has been fantastic work during the 10.x release line to get it stable and I feel like we can make that code easier by having a special branch for root nodes as sub-trees in our reconciler.

// This is not possible currently
<TreeA>
  <Foo />
  <root>
    <TreeB />
  </root>
</TreeA>

If we follow that thought further we could theoretically even look into switching renderes on the fly if there is any attached to the root node. A use case for that would be to switch to rendering into a canvas element somewhere in the middle of the tree.

What else can we do?

The above list is already a lot of work and will keep us busy for months but there may be stuff I've missed. Again, the points mentioned here is a collection of ideas and not a definitive feature set for Preact 11.

mprast commented 3 years ago

ok last comment I'll leave on this issue - @PuruVJ installing @types/react worked, but is that the 'official' way to get types to line up? I would imagine that would cause problems when trying to use imported library methods with preact components, etc. or did you mean to type @types/preact in your first post?

@o-alexandrov confirmed again this doesn't work on my project; when I have time I'll put up a minimal repro. Just an example of what I'm seeing - emotion tries to import from the top-level JSX namespace but preact/compat doesn't expose that. These types of issues have been well documented via PRs and issues on this repo, so if you're interested to learn more you can check those out too (see here and here for example)

o-alexandrov commented 3 years ago

@mprast I'm using emotion w/ Preact and haven't had any issues except one that was resolved here.

Since your issue seems to be related only to typings, it might be a good idea to revisit preact's TypeScript guide.

PuruVJ commented 3 years ago

ok last comment I'll leave on this issue - @PuruVJ installing @types/react worked, but is that the 'official' way to get types to line up? I would imagine that would cause problems when trying to use imported library methods with preact components, etc. or did you mean to type @types/preact in your first post?

@o-alexandrov confirmed again this doesn't work on my project; when I have time I'll put up a minimal repro. Just an example of what I'm seeing - emotion tries to import from the top-level JSX namespace but preact/compat doesn't expose that. These types of issues have been well documented via PRs and issues on this repo, so if you're interested to learn more you can check those out too (see here and here for example)

I said @types/react only. I use it in a project with 40+ components. Some stuff, Prreact doesn't have, so Using React. types help. Its actually better than either Preact types or React types

StephanBijzitter commented 3 years ago

I've seen some talk on dropping IE11 support and would like to chime in a bit:

I myself would still like IE11 support, although it really isn't a hard requirement. My current app is currently barely functional in IE11 anyway (for example: a Service Worker is required to do anything meaningful), but it's better than a blank page because the app doesn't even load.

I really wouldn't mind it - at all - if IE11 support was removed from the core package and made optional in some way. But yeah, if that's too complex for the few people like me.... dropping support is not an issue, I can quite easily write a single IE11-only html file with some browser upgrade instructions inlined.

So yeah, just wanted to say: as a person who does still use IE11, I wouldn't hold a grudge if support for it was dropped. Maybe someday Microsoft will accidentally update IE11 to a fully broken state; that'd be amazing.

ShadiestGoat commented 3 years ago

Hey there is talk about 'moving component out of core', what does that mean?

PuruVJ commented 3 years ago

It means, that the Component class, using which you create class components(class MyComp extends Component {, will be moved to preact/compat rather than plain import { Component } from 'preact'

ShadiestGoat commented 3 years ago

Wait.. why? Is that not the official way to do it? Are function components a better practice?

ghost commented 3 years ago

Since the introduction of hooks, function components have become more popular than class components.

The most common talking point is that many projects (including preact-devtools) don't use a single class component. The natural question that arises here is why we need to still pay for the cost the class API regardless. Ideally only the features that are actually used should be shipped to users.

ShadiestGoat commented 3 years ago

Ah gotcha, I will be switching my apps then :flushed:

hbroer commented 3 years ago

Hooks and all that stuff are too "magical" to me :-D Not the way I like to build applications. I don't like that I will need a 2nd view framework related import statement because of that. Treeshaking should do its job, why excluding it from the main package? How does anybody know what is (really) used more? Does that hooks thing even work with typescript in strict mode and eslint ts recomendations? ^^

ShadiestGoat commented 3 years ago

@hbroer yes the whole hooks thing works perfectly with typescript ^^

hbroer commented 3 years ago

aside of that I can't find any good example the only one I saw is using any like smarties :-D But I don't see any benefits of hooks too. It is less descriptive and magicaly coupled. Both should be avoided. Just a few less characters to type in some usecases maybe. It also has no good way to add dependency inversion. Instead you depend on context (never liked that and I don't use it).

ShadiestGoat commented 3 years ago

I mean I wouldn't be arguing which one is better haha.. I used to use the classes, and it seemed very inuative, and quite simple to understand. I started up on classes, but then I looked into hooks, and yes its more complicated sometimes, but honestly, I like it more now that I'm used to it. I don't know why the only example you found was using any, but like the guide has a pretty good explanation haha...

hbroer commented 3 years ago

(https://preactjs.com/guide/v10/hooks) has a pretty good explanation haha...

You are right they work. I did not try that. Works better than it looks in a real world example I saw before. Still no dependency inversion, so it is a nogo for me (I use class property decorators) ;-) I can see some benefits but none of them work for my applications. The problem in my usecase is that the main component I really need is the renderer and its diffing (+ Component). I whish I would not be too lazy to make my own view renderer without the need to be compatible with the React universe :-D

Btw I only said that it is sad that the main functionality is excluded from the main package. I have no benefit of that. No one has. It only results in touching all tsx files to correct the import path. If someone doesn't use Component then it would be striped out by the tree shaking of the bundler AFAIK. IMO the hooks are a new kind to make a application. It should be it's own framework. Now we have two in one and the way I like feels like it is getting obsolete.

ettoredn commented 2 years ago

Is 11 ever going to happen?

JoviDeCroock commented 2 years ago

very much working on it @ettoredn still an open-source project and we have a job outside of this as well https://github.com/preactjs/preact/tree/main

nullpaper commented 2 years ago

Is maintaining React compatibility a goal for Preact 11?

The release of React 18 29 Mar 2022 puts the latest version of React a fair way ahead of (or diverged from) Preact X, including several new hooks (e.g. useID, useSyncExternalStore and others) and a host of new features, including a bunch of stuff at the core of how components render.

It doesn't look like much of the React 18 new capability is considered as part of the Preact 11 roadmap (at least not from what I could tell in this ticket, or anywhere else obvious), and compatability issues are already showing up in libraries moving to support React 18 features.

rschristian commented 2 years ago

The release of React 18 29 Mar 2022 puts the latest version of React a fair way ahead of (or diverged from) Preact X, including several new hooks (e.g. useID, useSyncExternalStore and others)

There have been a few PRs working on this, see #3402, #3498, and #3510. That last PR in particular was a fair handful of those hooks.

It doesn't look like much of the React 18 new capability is considered as part of the Preact 11 roadmap

Those last two were aimed at a Preact X release, not 11 (master branch is X, main is 11)

cortopy commented 2 years ago

@rschristian it's very promising we may get react 18 support before preact 11, but all the PRs you mention are now closed. Do you know why? there doesn't seem to be any alternative PR that supersede

HummingMind commented 2 years ago

Is there any information on Preact 11? Features planned, release date, etc.? Thank you.

ettoredn commented 2 years ago

It has come to my attention that there exists a thing called React.

kurtextrem commented 2 years ago

It has come to my attention that there exists a thing called React.

Is this a troll comment?

Is there any information on Preact 11? Features planned, release date, etc.? Thank you.

Not sure if related to Preact 11, but take a look at Jason's Twitter: https://twitter.com/_developit/status/1549001036802625536?t=dKVS_S-_8fDUS-x8qO7Ymw

JoviDeCroock commented 2 years ago

Let me write a bit of an update on this topic as we have been hard at work, currently we have a branch in parallel to master named main which contains the code for v11.

As you might have noticed we are still very actively releasing on Preact X because we have been finding a lot of things we can improve. Among these releases have been several fixes to event-handling, repeating renders, consistency of hooks, .... I personally think there is a few more low hanging fruits we can tackle in X. These changes are then copied over to 11 because we want to make stuff great and usable for all of you.

Current changes in 11:

These are the externally facing changes that most of you will notice from the start, however internally we have changed a lot. We have moved to a backing VNode structure. This means that rather than seeing a lot of GC runs during diffing/... we will retain our objects longer (for their entire lifetime), currently in Preact we use the elements from createElement and assign our runtime onto those. This is the first improvement here, which reduces memory usage by a lot and should really bootstrap us to make all the changes we want to do in the future.

We have introduced a new way of diffing arrays of children based on a skew indicator, this looks like a superficial algo fix but it solves some consistency problems that are currently present in X in relation to focus loss or remounting children that shouldn't be.

We have reduced the possible code-paths that are traversed during hydration/initial mount to guarantee that when your server-side HTML hydrates or when your client-side app renders for the first time that we follow the most optimal path, noone likes waiting for their application 😄

I hope this short summary is at least sufficient for all of you, we have a first experimental tag released on npm which we have used to update preact-devtools and prefresh already for when we feel ready to release to the general public.

The tweet from Jason there can also be used with Preact X

gu-stav commented 2 years ago

@JoviDeCroock Thank you so much for the update. I guess I can speak for many people, when I say: thank you for your hard work and the amount of thinking + experimentation you all put into moving the framework/ library (don't pin me on that :D) forward.

ConradSollitt commented 2 years ago

Hi @JoviDeCroock

I've read briefly about this update recently and your post is the most detailed I've read so far so thanks for this info.

I have a few questions (hopefully quick):

Great job to you and everyone who worked on the new version. I'm excited to try it out as I'm aware it can have significant performance improvements for some apps!

JoviDeCroock commented 2 years ago

Typically through CDN's (or at least how they currently work) you won't benefit from tree-shaking so class-components won't be shaken out but that improvement could be offset by just the download being very fast from your CDN 😄 Currently we have 1 experimental publish (https://cdn.skypack.dev/-/preact@v11.0.0-experimental.1-ZmOf9FnGoQoU8MTBuc9y/dist=es2019,mode=imports/optimized/preact.js) but that is a bit outdated compared to the current main so feel free to wait for our alpha's to start appearing.

I will try and make a set of open-tasks with the team but can't promise anything atm 😅 personally it's a very busy time

HummingMind commented 2 years ago

Great information.

Thank you!

lolyinseo commented 2 years ago

I want to write a few thoughts about Suspense\Lazy. I'm not a pro, so I apologize right away, these are just the ideas of a preact user ...

  1. If Suspense work with Promise why does it only handle only one state == fulfilled ? isn't it logical use all state?
html`
<${Suspense} rejected=${Reject} pending=${Pending} />
  <${Foo}>
    <${SomePromiseComponent /}>
  </${Foo}>
</${Suspense}>
`
  1. What happens if Suspense will contains several Promise?
html`
<${Suspense} fallback={<div>loading...</div>} />
  <${Foo}>
    <${SomePromiseComponent1} />
    <${SomePromiseComponent2} />
    <${SomePromiseComponent3} />
    <${SomePromiseComponent4} />
  </${Foo}>
</${Suspense}>
`
  1. Why can't I use Component as fallback like that?
class Fallback extends Component {
    render() {
        return html`<div>loading...</div>`;
    }
}

html`
<${Suspense} fallback=${Fallback} />
  <${Foo}>
    <${SomePromiseComponent} />
  </${Foo}>
</${Suspense}>
`
  1. If Lazy It's just Promise, It could just get rid of that object and let render return Promise?
class Pending extends Component {
    render() {
        return html`<div>loading...</div>`;
    }
}

class SomePromiseComponent extends Component {
    render() {
        return new Promise(resolve => resolve(html`<div>I.am Ready to GO...</div>`)) ;
    }
}

html`
<${Suspense} pending=${Pending} />
  <${Foo}>
    <${SomePromiseComponent} />
  </${Foo}>
</${Suspense}>
  1. or even easier without Suspense and Lazy! if let's render return Promise we don't need these objects
class Reject extends Component {
    render() {
        return html`<div>I am Error</div>`;
    }
}

class Pending extends Component {
    render() {
        return html`<div>loading...</div>`;
    }
}

class SomePromiseComponent extends Component {
    render() {
        return new Promise(resolve => resolve(html`<div>I.am Ready to GO...</div>`)) ;
    }
}

html`
  <${Foo}>
    <${SomePromiseComponent} pending=${Pending} rejected=${Reject}/>
  </${Foo}>
intrnl commented 2 years ago

as Suspense is in preact/compat, it follows React's behavior as to how it works, and that should pretty much answer the question as to why things behaves as it is now, but here's some additional details:

  1. Suspense can keep track of multiple promises that are being thrown, this is handled just fine and acts like Promise.all
  2. Passing an element instead of component for the fallback prop allows for passing props easily to the fallback component, and also allows for non-components as well. Sure, there could be a check for either a component or an element but IMO it's best to just stick to one usage
  3. This makes both pending and rejected a reserved property, which isn't really a good idea.
lolyinseo commented 2 years ago

Suspense is in preact/compat, it follows React's behavior as

Suspense will be in the core, and as I show next, this entity is not needed at all if Component are allowed to return Promise.. (not sure about Component , maybe it's better class SomePromiseComponent extends Suspense)

Suspense can keep track of multiple promises that are being thrown, this is handled just fine and acts like Promise.all

Ok. If I want a behavior of Promise.any?, another constraint that removes the use render (return new Promise())

Passing an element instead of component for the fallback prop allows for passing props easily to the fallback component, and also allows for non-components as well. Sure, there could be a check for either a component or an element but IMO it's best to just stick to one usage

Nope. If I use logic on classes I expect it to work everywhere. I will even say more, it can be used now, but it looks weird... <${Suspense} fallback=${html<${Fallback} />}>

This makes both pending and rejected a reserved property, which isn't really a good idea.

It does not matter. Names can be anything...

intrnl commented 2 years ago

most likely it'd just be implemented as part of compat, we could probably do it now actually

the use hook seems like it's just an alias for throwing, so you can get most of the way there by doing

function use (promise) {
  if ('then' in promise) {
    throw promise;
  }

  return promise;
}

there's some stuff not covered like caching the result of a promise but that can be easily added

there's one part of the use proposal that I'm not sure if Preact should implement properly though, and it's the part involving monkeypatching fetch API

karlhorky commented 1 year ago

Any plans on a React Server Components implementation in Preact for v11? Would be great to get async function components that run server-only, DX is 🔥 https://twitter.com/karlhorky/status/1633104690907947008

I saw this but it seems like there's not much going on there:

quantizor commented 1 year ago

Any plans on a React Server Components implementation in Preact for v11? Would be great to get async function components that run server-only, DX is 🔥 https://twitter.com/karlhorky/status/1633104690907947008

I saw this but it seems like there's not much going on there:

If this does move forward, please don't kill use of the useContext API like React did.

ShinobiWPS commented 1 year ago

Any plans on a React Server Components implementation in Preact for v11? Would be great to get async function components that run server-only, DX is 🔥 https://twitter.com/karlhorky/status/1633104690907947008

I saw this but it seems like there's not much going on there:

In another section i've read the reply of a Preact main contributor saying that with preact-iso + Suspense you can achieve 90% of benefits of async server components, but honeslty i didn't get how

PodaruDragos commented 7 months ago

I din't see any mentions of performance on 11. Are there any performance improvements ? I saw that there are some v11- prefixed branches and judging by the names I assume some performance improvements were tried and/or considered.

PS: I would really like to see the new goodies improving perf

bensaufley commented 5 months ago

I don't see this May 24 blog post linked here so it makes sense to share it for those of you wondering when Preact 11 might be coming. Tl;dr: it's still coming but a lot of the things they thought would be breaking changes have been or will be released in X, so 11 will be less of a major set of changes whenever it arrives:

This doesn't mean that Preact 11 won't happen but it might not be the thing that we initially thought it would be. Instead, we might just drop IE11 support and give you those performance improvements, all while giving you the stability of Preact X.

9Morello commented 1 month ago

Is React Compiler support on the roadmap for Preact?

rschristian commented 1 month ago

@9Morello I don't believe anyone's looked too closely into it yet, but as the compiler gets closer to full release we'll definitely be seeing if we can support it. No guarantees of course, but from what I've seen so far, it seems fairly likely.

Doesn't seem like it'll need to go in v11 either, v10 might be able to support it.