Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: forgetti/runtime #2

Open
lxsmnsyc opened this issue Mar 17, 2023 · 2 comments
Open

Feature: forgetti/runtime #2

lxsmnsyc opened this issue Mar 17, 2023 · 2 comments

Comments

@lxsmnsyc
Copy link
Owner

lxsmnsyc commented Mar 17, 2023

This is a thread discussing the introduction of forgetti/runtime, which would provide APIs that aren't achievable alone by the compiler.

$$equals(a, b)

Object.is is the slowest of the three comparison methods, the other being == and ===. equal provides a short path to allow using referential equality on non-number types.

Ideal implementation would look like:

function equals<T, R>(a: T, b: T) {
  return a === b || (a !== a && b !== b);
}

$$cache(useMemo, size)

  • This simplifies the output for the root cache.
  • Also adds other fields that may be useful (to be discussed in the latter part of the list).

$$effect(effectHook, index, callback, dependency)

  • This allows effect hooks to be unlocked from the hook rules.
  • Relies on the new $$cache

$$childCache(size)

  • Allows for 3P hooks to unlock its own hook rules by accessing the underlying root or parent cache. Previously it created its own root cache.

$$memo(memoHoc, Component, keys)

  • Memoizes components with monomorphic props.

Template splitting

This is still a pending design. The aim is for to split the template part from the logical part, producing a stateless memoized component. The design has the following considerations:

Splitting strategies

Per-element split

This solution aims to split every JSX element to be a standalone memoized component. This is definitely granular, but may consume too much memory

Singular template split

This solution only splits for the top-level JSX element. It has the same memoization process as the first solution except that the entirety is now grouped and is probably cheaper.

Component reference checking

This solution only applies memoization for potentially unmemoized components. The split creates a wrapper component at top-level and uses $$memo. Under the hood, $$memo decides if it should memoized the component by checking some key properties (either checking the component's name via React or assigning a symbol).

New JSX runtime

Maybe worth looking at?

Inspiration:

@aidenybai
Copy link

This proposal looks good! Just some thoughts and questions:

  • In the context of $$childCache, I don't completely understand how accessing the underlying root or parent cache would improve the situation compared to creating a new root cache. Could you provide more information on the benefits of this change and any potential trade-offs?

  • For unlocking effect hooks from the hook rules using the new $$cache, could you elaborate on the advantages this would provide? Are there any potential drawbacks or scenarios where this may not be desirable?

  • Per-element split could just be static hoisting here and it should be better than memo IMO:

const div = <div>foo</div>;

function Component() {
	return div
}
  • Introducing a new JSX runtime is an interesting idea. What specific improvements do you envision in this new runtime that would justify the effort required to develop it?

  • On a side note, it's theoretically it's possible to use million optimizations here given that forgetti can produce stateless components. Maybe I will explore this as part of million/react

@lxsmnsyc
Copy link
Owner Author

lxsmnsyc commented Mar 18, 2023

In the context of $$childCache, I don't completely understand how accessing the underlying root or parent cache would improve the situation compared to creating a new root cache. Could you provide more information on the benefits of this change and any potential trade-offs?

As you know, forgetti generates a root cache at the component which is what powers the memoization process. The thing with this is that it is sophisticated to work for branched statements (if-else) and loop statements (for) by producing a "branched cache", which allows useMemo, useCallback and useRef to be called without being restricted by the hook rules! This is what I call "unlocking" because now the hooks aren't as strict as before.

The problem with other hook-like functions is that right now, forgetti assumes that the hook will still be relying on its rules. On top of that, hooks generate a root cache on their own, essentially "locking" themselves since cache creation relies on useMemo. Like I mentioned with branched caches, ideally forgetti should produce a cache based on its parent cache, rather than creating a cache of its own.

You can read more about it here: https://github.com/lxsmnsyc/forgetti#branched-caching

For unlocking effect hooks from the hook rules using the new $$cache, could you elaborate on the advantages this would provide? Are there any potential drawbacks or scenarios where this may not be desirable?

Currently forgetti relies on calling effect hooks directly, which means it is still "locked". The proposed solution here is to declare an effect once, per kind, and then provide an "array" that would push the pending effects for the effect to run. It would look something like this barely:

const effects = useComposedEffect(useEffect, size);

effects[0] = [myEffect, dependencies];

Per-element split could just be static hoisting here and it should be better than memo IMO:

Yeah this is probably something I missed. Currently forgetti progressively memoizes the JSX expression, but of course for static JSX it is still included in the cache, but yes ideally it should be hoisted as well as literals and guaranteed literals.

Introducing a new JSX runtime is an interesting idea. What specific improvements do you envision in this new runtime that would justify the effort required to develop it?

This one is for pre-wrapping the components by the memo API before being passed to the VDOM object. Funnily enough I think it wouldn't work w/o explicitly defining the memo HOC.

On a side note, it's theoretically it's possible to use million optimizations here given that forgetti can produce stateless components. Maybe I will explore this as part of million/react

Yeah but take note though that I think million relies on serializable data. forgetti already has the memoization step, what forgetti only lacks is the ability to prevent re-rendering that which is only achievable through the memo HOC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants