React: RFC: ReactFiberReconciler release artifact

Created on 3 Mar 2017  ·  51Comments  ·  Source: facebook/react

This issue is intended to be a discussion for how to distribute the ReactFiberReconciler.js file and dependencies for custom renderers.

Currently first-class renderers within the React codebase/Facebook ecosystem do not have any concerns for this because of Haste / access to the React.js build + publish tooling within this codebase.

3rd party renderers are currently adding react-dom to their dependency list and requiring react-dom/lib/ReactFiberReconciler.js to build and expose their custom renderer.

Ideally, ReactFiberReconciler.js would be distributed with the react package or as a standalone react-fiber-reconciler package. Whether this is at react/reconciler.js or react/lib/reconciler.js idk. I think at the root is better to continue the discouragement of looking in react/lib/* for anything.

I understand flat bundles are coming, too. I don’t know if that should block this or if this could be a flat bundle, or any other alternative.

Is this something the team is ready to commit to and support if we came to a decision on approach and I put together a PR?

Related issues:

  • #6795 Create Separate Copies of Each Renderer
  • 5472 Include flow type definitions (flow type distribution proposal at https://github.com/facebook/react/issues/5472#issuecomment-282394248)

  • @sebmarkbage @spicyj

Core Utilities Feature Request

Most helpful comment

@gaearon, @iamdustan, and I just chatted about this. Here's a quick recap (please feel free to correct any missing or inaccurate info).

Overall direction:

  • React's Rollup build scripts will likely become a new NPM package that can be consumed by third party renderers (eg react-custom-renderer).
  • The fiber reconciler may be directly accessible from this module (eg react-custom-renderer/reconciler).

    • This was a bit controversial.

    • Dan didn't see the value of exposing this directly and was concerned about increased complexity.

    • Brian thought that exposing the reconciler (and its Flow types) directly would give users a way of "ejecting" and using their own build scripts instead of our Rollup script.

    • Dustin mentioned a use case of creating a custom renderer for internal use only, that hooked directly into a larger application's build tooling (and thus wouldn't require a separate Rollup build step).

  • It would be nice if reconcile Flow types were exposed for third party renderers to use.

Other thoughts:

  • The ART renderer might make a nice initial test case since it's the least coupled to the internals of the React git repo.
  • React core renderers (eg dom, native) should consume this new script by way of virtual requires (eg react-custom-renderer/reconciler would be re-routed to consume the local source, but would enable us to do some dogfooding).

Open questions:

  • How should we handle React-specific stuff (eg error codes, results.json bundle sizes, etc)

Next steps:

  • One of us will create either a proof-of-concept PR or a more in-depth proposal for how to proceed and this group will review.

All 51 comments

Here's how I see the goal repo layout to be:

1) Lerna style repo layout.
2) One package is the reconciler core which react-dom etc. depend on. It will be a separate package like react-something-reconciler. (Not react since that doesn't let renderers release new versions separately without being in lock-step with the main package.)
3) For packages like react-dom we'll only depend on react-something-reconciler as a devDependency.
4) In prepublish for react-dom we'll actually compile a bundled (flat bundle) copy e.g. with Rollup. That includes the entirety of react-something-reconciler but allows us to DCE and inline renderer specific pieces.

Now other renderers can have strong dependencies on react-something-reconciler but I'm not sure we'll fully support that dependency being shared outside of devDependencies since it might have global state that is not safe to share with two renderers at the same time. Compiling it into a single package avoids that problem.

We could also expose our build tooling (Rollup + DCE toolchain) as a build-time dependency (just like react-scripts from Create React App). This way we could share the same setup between our and third-party renderers.

Basically, this:

mkdir my-renderer
cd my-renderer

npm init -y
npm install --save-dev react-renderer-scripts

edit hostConfig.js # write your renderer

./node_modules/react-renderer-scripts build ./hostConfig.js

# produces UMDs:
#   build/my-react-renderer.js
#   build/my-react-renderer.min.js

Awesome. Thanks Sebastian and Dan.

Is react-renderer-scripts a future step or intended to be in the initial publishing?

Are there any blocking issues that need to land before this can be tackled?

That's just a wild suggestion, I don't know if we're going to do it this way or not.
We're going to start working on flat bundle infra next week with @trueadm so we'll keep you posted.

How would it work if you wanted to still use some of ReactDOM's internals, but switch things up a little? Just fork it?

Yea.

Any progress on the issue?
It is well desired feature, one way or another.

No, if there was progress it would be on this issue :-)
If you have ideas on how to make it work or want to give it a try please feel free to!

Is it reasonable to build renderers using the ReactFiberReconciler from alpha.3? It’s fine if it’s unstable, just wondering if there is a final decision on whether the reconciler will be supported as a public API at some point.

It will be supported as public API, we just haven't decided how to expose it. It's okay to use an old exposed version in the meantime.

@gaearon, @iamdustan, and I just chatted about this. Here's a quick recap (please feel free to correct any missing or inaccurate info).

Overall direction:

  • React's Rollup build scripts will likely become a new NPM package that can be consumed by third party renderers (eg react-custom-renderer).
  • The fiber reconciler may be directly accessible from this module (eg react-custom-renderer/reconciler).

    • This was a bit controversial.

    • Dan didn't see the value of exposing this directly and was concerned about increased complexity.

    • Brian thought that exposing the reconciler (and its Flow types) directly would give users a way of "ejecting" and using their own build scripts instead of our Rollup script.

    • Dustin mentioned a use case of creating a custom renderer for internal use only, that hooked directly into a larger application's build tooling (and thus wouldn't require a separate Rollup build step).

  • It would be nice if reconcile Flow types were exposed for third party renderers to use.

Other thoughts:

  • The ART renderer might make a nice initial test case since it's the least coupled to the internals of the React git repo.
  • React core renderers (eg dom, native) should consume this new script by way of virtual requires (eg react-custom-renderer/reconciler would be re-routed to consume the local source, but would enable us to do some dogfooding).

Open questions:

  • How should we handle React-specific stuff (eg error codes, results.json bundle sizes, etc)

Next steps:

  • One of us will create either a proof-of-concept PR or a more in-depth proposal for how to proceed and this group will review.

I thought about it some more and I'm still not convinced we need to expose the reconciler directly to be importable (even if the import is "fake").

This approach makes more sense to me here. Instead of making a magic import work, or actually exposing it on npm, we can inject it.

Let's say the renderer has to have src/index.js. We can enforce that it should be a module that exports a factory. The reconciler function is the argument to it. The return value is the real export.

For example, for ReactDOM index.js would be like

export default function(Reconciler) {
  const DOMRenderer = Reconciler({
    // host config
  });

  return {
    render(element, container) {
      // ...
    },
  };
}

This IMO solves all problems:

  • There are no magic imports.
  • There is no way to import reconciler directly at all (which is intentionally unsupported because it is unsafe, and people will abuse it and bump into issues if we let them do it).
  • It lets you export anything in practice so you can build your own facade around the renderer.

And it doesn't preclude us from exposing reconciler directly in the future if those use cases prove to be important. (So far it does not seem necessary for our renderers but we'll get more feedback.)

Both Micro and Babel are precedents for this kind of API. (Micro asks you to export a function that takes request and response, Babel asks you to export a function that takes Babel helpers.)

I don't know if I got your proposal right. Can you explain how a custom renderer (outside the react package) would receive the injection?
Or are custom renderers something that only maybe get enabled after more evaluation in the future?

Three would be a build command that looks at the entry point and links it together with the reconciler source, then compiles with Rollup.

Would be great to have some way of working with Reconciler. As far as I could see atm you can't write your own custom renderers with React 16 unless you maintain your own fork which sucks.

EDIT: for now downgrade to [email protected] which has the lib directory and hence this export.

This is what the issue is about :-) We agree it would be great, but as you can see from the issue, some work needs to be done to make it possible.

FYI I have started working on this a teeny bit. I’m currently planning on hacking on this more during React Rally in a few days. 🤞

Any feedback when the ReactFiberReconciler will be available in any public way? :) thanks

Hi @mcz – please read the previous comments on this issue to understand the status.

Are these rollup scripts indispensable to create an own renderer?

Not necessarily.. But just putting the code into an npm package won’t work because we have some singleton modules that would break if shared between renderers.

Maybe we can ship a version that’s resilient to this by compiling a bundle with the reconciler itself as an entry point but with the whole code wrapped into an extra factory.

Actually this sounds really promising and simple to me ^^
Do you want to give it a try?

That sounds way easier than the custom build tool that still needs access to the reconciler somehow 😅

@iamdustan Wanna try to make that bundle? Would need to have some special post-processing in the build script to put a closure around it.

@gaearon putting the first 30 minutes of day towards it right now.

We already have similar postprocessing for some cases here: https://github.com/facebook/react/blob/master/scripts/rollup/build.js#L80

s/30 minutes/2 hours.

Let me know if you want a hand, @iamdustan. Sounds like a fun project.

I thought our goal with Fiber was to avoid singleton modules? So that you can instantiate it if desired; it's just a little slower that way.

I’m not sure if it was the goal (maybe?) but there are a few places where we have singletons in practice. I’m not sure how we can reliably catch them except for completely forbidding module level variables? Also not sure what the benefit is if we have flat bundles and can still do the wrapping trick for third parties.

I felt strongly about making things instantiable; @sebmarkbage felt strongly that build-time injection was the way to go. We compromised by making factory functions like ReactFiberReconciler() (and Scheduler, etc) which _can_ be used at runtime but with the recommendation that you inline things at build time. I don't think there is necessarily a good way to enumerate them but we can at least fix things as they come up.

I see. In any case seems like we want to distribute reconciler as a separate entry point which means it needs to be its own bundle. If we later fix those places up we can remove the extra closure. Does this make sense?

I'm not sure I understand you. I am suggesting that each module export a factory method (if it has module-level state) so that you can create multiple renderers with one copy of the code. If we bundle an entire renderer with rollup and run Closure on it the extra constructors should disappear.

Fwiw a wasm version of this would have to be compiled as a singleton and "instantiated" multiple times so just wrapping everything would be inline with that model.

I think it's mostly just the context stack and such that are not instantiated right now.

I am suggesting that each module export a factory method (if it has module-level state) so that you can create multiple renderers with one copy of the code

I agree with that. I am saying that this shouldn't need to prevent @iamdustan working on https://github.com/facebook/react/issues/10758 because in both cases we’ll want to have that bundle. We can remove the hack with wrapping the whole bundle when we wrap module level state at the source code level, but we need the bundle anyway.

I have a question which is a _slight_ tangent, apologies in advance if this is the wrong place to raise it:

Can someone please point me to documentation on how to build a custom renderer from the ground up? I don't see it on the React docs and it's tricky to reverse engineer from looking at the ART renderer alone.

Fwiw my rough goal is to build a React interface to PIXI in order to address major performance issues - which I hope were caused by using the DOM-renderer

@dakom I don't think there's an official documentation yet, but I had the same interrogation and documented my findings inside my custom renderer source code, you can read the comments here: manaflair/mylittledom. Caution, some things might not be entirely accurate 😃

Good resources to understand how should be implemented the custom-defined functions are the TestRenderer and the NoopRenderer, since they're both pretty simple.

oooh just found this: https://github.com/iamdustan/tiny-react-renderer

But that was total luck. I feel like, if there's no official documentation, there's got to be some consolidated knowledgebase lurking somewhere... ;)

@dakom how did you stumble across that? I must be really bad at “marketing” 🙃

hehe no worries - my learning path in JS is super weird. Diving in from a decade out of the browser, the first modern JS things I was hearing about were like FRP and functional things. Then React. I'm hoping to learn what an Array is next ;)

Followup question - is there a good live forum to discuss building a renderer specifically? I have a feeling I'll have lots of little one-liner questions and this issue probably isn't the best place to post those, and it's outside the scope of most general React discussions

Reactiflux has a #react-internals channel. https://www.reactiflux.com/

You can also just file issues. We're happy to chat about renderers.

Hi, everyone. I am the maintainer of https://github.com/lavrton/react-konva/. react-konva works in the same way as react-art works. Right now with the new React v16, I wish to update react-konva as soon as possible. But if fact I have no idea how. I see new fiber version of react-art in the repo, but I don't how to apply it in a standalone library.

Should I just wait until the issue is resolved? Or are there any other ways to use react internals?

Please wait for https://github.com/facebook/react/pull/10758 to land and this issue to be closed.

fwiw I dumped the various links suggested so far into this gist, in case it helps anyone else to see them in one place: https://gist.github.com/dakom/85967eaa2cdb217a4d80eb57afd8e7b1

So far (very early on) it seems that comparing/contrasting gets a lot of mileage and I'm slowly filling in the blanks to build my first renderer - thanks to y'all! :)

I think we can close this since react-reconciler will ship with React 16.1.

I added a non-exhaustive API reference here.

https://github.com/facebook/react/tree/master/packages/react-reconciler#an-incomplete-reference

Was this page helpful?
0 / 5 - 0 ratings