Commit Graph

103 Commits

Author SHA1 Message Date
Sebastian Markbåge
2cc6d79c98 Rename onReadyToStream to onCompleteShell (#22443) 2021-09-27 12:38:22 -07:00
Justin Grant
c88fb49d37 Improve DEV errors if string coercion throws (Temporal.*, Symbol, etc.) (#22064)
* Revise ESLint rules for string coercion

Currently, react uses `'' + value` to coerce mixed values to strings.
This code will throw for Temporal objects or symbols.

To make string-coercion safer and to improve user-facing error messages,
This commit adds a new ESLint rule called `safe-string-coercion`.

This rule has two modes: a production mode and a non-production mode.
* If the `isProductionUserAppCode` option is true, then `'' + value`
  coercions are allowed (because they're faster, although they may
  throw) and `String(value)` coercions are disallowed. Exception:
  when building error messages or running DEV-only code in prod
  files, `String()` should be used because it won't throw.
* If the `isProductionUserAppCode` option is false, then `'' + value`
  coercions are disallowed (because they may throw, and in non-prod
  code it's not worth the risk) and `String(value)` are allowed.

Production mode is used for all files which will be bundled with
developers' userland apps. Non-prod mode is used for all other React
code: tests, DEV blocks, devtools extension, etc.

In production mode, in addiiton to flagging `String(value)` calls,
the rule will also flag `'' + value` or `value + ''` coercions that may
throw. The rule is smart enough to silence itself in the following
"will never throw" cases:
* When the coercion is wrapped in a `typeof` test that restricts to safe
  (non-symbol, non-object) types. Example:
    if (typeof value === 'string' || typeof value === 'number') {
      thisWontReport('' + value);
    }
* When what's being coerced is a unary function result, because unary
   functions never return an object or a symbol.
* When the coerced value is a commonly-used numeric identifier:
  `i`, `idx`, or `lineNumber`.
* When the statement immeidately before the coercion is a DEV-only
  call to a function from shared/CheckStringCoercion.js. This call is a
  no-op in production, but in DEV it will show a console error
  explaining the problem, then will throw right after a long explanatory
  code comment so that debugger users will have an idea what's going on.
  The check function call must be in the following format:
    if (__DEV__) {
      checkXxxxxStringCoercion(value);
    };

Manually disabling the rule is usually not necessary because almost all
prod use of the `'' + value` pattern falls into one of the categories
above. But in the rare cases where the rule isn't smart enough to detect
safe usage (e.g. when a coercion is inside a nested ternary operator),
manually disabling the rule will be needed.

The rule should also be manually disabled in prod error handling code
where `String(value)` should be used for coercions, because it'd be
bad to throw while building an error message or stack trace!

The prod and non-prod modes have differentiated error messages to
explain how to do a proper coercion in that mode.

If a production check call is needed but is missing or incorrect
(e.g. not in a DEV block or not immediately before the coercion), then
a context-sensitive error message will be reported so that developers
can figure out what's wrong and how to fix the problem.

Because string coercions are now handled by the `safe-string-coercion`
rule, the `no-primitive-constructor` rule no longer flags `String()`
usage. It still flags `new String(value)` because that usage is almost
always a bug.

* Add DEV-only string coercion check functions

This commit adds DEV-only functions to check whether coercing
values to strings using the `'' + value` pattern will throw. If it will
throw, these functions will:
1. Display a console error with a friendly error message describing
   the problem and the developer can fix it.
2. Perform the coercion, which will throw. Right before the line where
   the throwing happens, there's a long code comment that will help
   debugger users (or others looking at the exception call stack) figure
   out what happened and how to fix the problem.

One of these check functions should be called before all string coercion
of user-provided values, except when the the coercion is guaranteed not
to throw, e.g.
* if inside a typeof check like `if (typeof value === 'string')`
* if coercing the result of a unary function like `+value` or `value++`
* if coercing a variable named in a whitelist of numeric identifiers:
  `i`, `idx`, or `lineNumber`.

The new `safe-string-coercion` internal ESLint rule enforces that
these check functions are called when they are required.

Only use these check functions in production code that will be bundled
with user apps.  For non-prod code (and for production error-handling
code), use `String(value)` instead which may be a little slower but will
never throw.

* Add failing tests for string coercion

Added failing tests to verify:
* That input, select, and textarea elements with value and defaultValue
  set to Temporal-like objects which will throw when coerced to string
  using the `'' + value` pattern.
* That text elements will throw for Temporal-like objects
* That dangerouslySetInnerHTML will *not* throw for Temporal-like
  objects because this value is not cast to a string before passing to
  the DOM.
* That keys that are Temporal-like objects will throw

All tests above validate the friendly error messages thrown.

* Use `String(value)` for coercion in non-prod files

This commit switches non-production code from `'' + value` (which
throws for Temporal objects and symbols) to instead use `String(value)`
which won't throw for these or other future plus-phobic types.

"Non-produciton code" includes anything not bundled into user apps:
* Tests and test utilities. Note that I didn't change legacy React
  test fixtures because I assumed it was good for those files to
  act just like old React, including coercion behavior.
* Build scripts
* Dev tools package - In addition to switching to `String`, I also
  removed special-case code for coercing symbols which is now
  unnecessary.

* Add DEV-only string coercion checks to prod files

This commit adds DEV-only function calls to to check if string coercion
using `'' + value` will throw, which it will if the value is a Temporal
object or a symbol because those types can't be added with `+`.

If it will throw, then in DEV these checks will show a console error
to help the user undertsand what went wrong and how to fix the
problem. After emitting the console error, the check functions will
retry the coercion which will throw with a call stack that's easy (or
at least easier!) to troubleshoot because the exception happens right
after a long comment explaining the issue. So whether the user is in
a debugger, looking at the browser console, or viewing the in-browser
DEV call stack, it should be easy to understand and fix the problem.

In most cases, the safe-string-coercion ESLint rule is smart enough to
detect when a coercion is safe. But in rare cases (e.g. when a coercion
is inside a ternary) this rule will have to be manually disabled.

This commit also switches error-handling code to use `String(value)`
for coercion, because it's bad to crash when you're trying to build
an error message or a call stack!  Because `String()` is usually
disallowed by the `safe-string-coercion` ESLint rule in production
code, the rule must be disabled when `String()` is used.
2021-09-27 10:05:07 -07:00
Sebastian Markbåge
d47339ea36 [Fizz] Remove assignID mechanism (#22410)
* Remove pushEmpty

This is only used to support the assignID mechanism.

* Remove assignID mechanism

This effectively isn't used anyway because we always insert a dummy tag
into the fallback.

* Emit the template tag with an ID directly in pending boundaries

This ensures that assigning the ID is deterministic since it's done during
writing.

This also avoids emitting it for client rendered boundaries that start as
client rendered since we never need to refer to them.

* Move lazy ID initialization to the core implementation

We never need an ID before we write a pending boundary. This also ensures
that ID generation is deterministic by moving it to the write phase.

* Simplify the inserted scripts

We can assume that there are no text nodes before the template tag so this
simplifies the script that finds the comment node. It should be the direct
previous child.
2021-09-24 10:22:02 -04:00
Andrew Clark
82c8fa90be Add back useMutableSource temporarily (#22396)
Recoil uses useMutableSource behind a flag. I thought this was fine
because Recoil isn't used in any concurrent roots, so the behavior
would be the same, but it turns out that it is used by concurrent
roots in a few places.

I'm not expecting it to be hard to migrate to useSyncExternalStore, but
to de-risk the change I'm going to roll it out gradually with a flag. In
the meantime, I've added back the useMutableSource API.
2021-09-21 20:38:24 -07:00
salazarm
64e70f82e9 [Fizz] add avoidThisFallback support (#22318) 2021-09-20 15:44:48 -04:00
Andrew Clark
86b3e2461d Implement useSyncExternalStore on server (#22347)
Adds a third argument called `getServerSnapshot`.

On the server, React calls this one instead of the normal `getSnapshot`.
We also call it during hydration.

So it represents the snapshot that is used to generate the initial,
server-rendered HTML. The purpose is to avoid server-client mismatches.
What we render during hydration needs to match up exactly with what we
render on the server.

The pattern is for the server to send down a serialized copy of the
store that was used to generate the initial HTML. On the client, React
will call either `getSnapshot` or `getServerSnapshot` on the client as
appropriate, depending on whether it's currently hydrating.

The argument is optional for fully client rendered use cases. If the
user does attempt to omit `getServerSnapshot`, and the hook is called
on the server, React will abort that subtree on the server and
revert to client rendering, up to the nearest Suspense boundary.

For the userspace shim, we will need to use a heuristic (canUseDOM)
to determine whether we are in a server environment. I'll do that in
a follow up.
2021-09-20 08:31:02 -07:00
Andrew Clark
8209de2695 Delete useMutableSource implementation (#22292)
This API was replaced by useSyncExternalStore
2021-09-19 21:11:50 -07:00
Ricky
263cfa6ecb [Experimental] Add useInsertionEffect (#21913) 2021-09-14 10:27:09 -04:00
Andrew Clark
77912d9a05 Wire up the native API for useSyncExternalStore (#22237)
Adds useSyncExternalStore to the internal dispatcher, and exports
the native API from the React package without yet implementing it.
2021-09-07 10:20:24 -07:00
Sebastian Markbåge
321087d134 [Fizz] Don't add aborted segments to the completedSegments list (#21976)
* Don't add aborted segments to the completedSegments list

* Update error message to include aborted status
2021-07-27 21:53:37 -04:00
Deniz Susman
3f62dec84a Typo fix (#21729)
emitted instead of emmitted
2021-07-19 17:19:52 -04:00
Ricky
14bac6193a Allow components to render undefined (#21869) 2021-07-13 15:48:11 -04:00
Sebastian Markbåge
502f8a2a07 [Fizz/Flight] Don't use default args (#21681)
* Don't use default args

* Hoist out creation for better inlining

The closures prevent inlining otherwise.
2021-06-14 15:28:20 -07:00
Sebastian Markbåge
dbe3363ccd [Fizz] Implement Legacy renderToString and renderToNodeStream on top of Fizz (#21276)
* Wire up DOM legacy build

* Hack to filter extra comments for testing purposes

* Use string concat in renderToString

I think this might be faster. We could probably use a combination of this
technique in the stream too to lower the overhead.

* Error if we can't complete the root synchronously

Maybe this should always error but in the async forms we can just delay
the stream until it resolves so it does have some useful semantics.

In the synchronous form it's never useful though. I'm mostly adding the
error because we're testing this behavior for renderToString specifically.

* Gate memory leak tests of internals

These tests don't translate as is to the new implementation and have been
ported to the Fizz tests separately.

* Enable Fizz legacy mode in stable

* Add wrapper around the ServerFormatConfig for legacy mode

This ensures that we can inject custom overrides without negatively
affecting the new implementation.

This adds another field for static mark up for example.

* Wrap pushTextInstance to avoid emitting comments for text in static markup

* Don't emit static mark up for completed suspense boundaries

Completed and client rendered boundaries are only marked for the client
to take over.

Pending boundaries are still supported in case you stream non-hydratable
mark up.

* Wire up generateStaticMarkup to static API entry points

* Mark as renderer for stable

This shouldn't affect the FB one ideally but it's done with the same build
so let's hope this works.
2021-06-14 12:54:30 -07:00
Dan Abramov
cc4d24ab0b [Fizz] Always call flush() if it exists (#21625) 2021-06-04 20:17:57 +01:00
Sebastian Markbåge
e0d9b28999 [Fizz] Minor Fixes for Warning Parity (#21618) 2021-06-03 19:54:31 -04:00
Sebastian Markbåge
1b7b3592f4 [Fizz] Implement Component Stacks in DEV for warnings (#21610)
* Implement component stacks

This uses a reverse linked list in DEV-only to keep track of where we're
currently executing.

* Fix bug that wasn't picking up the right stack at suspended boundaries

This makes it more explicit which stack we pass in to be retained by the
task.
2021-06-03 13:02:41 -07:00
Sebastian Markbåge
4a8deb0836 Switch the isPrimaryRender flag based on the stream config (#21357) 2021-04-26 22:37:05 -04:00
Sebastian Markbåge
bd4f056a3e [Fizz] Implement lazy components and nodes (#21355)
* Implement lazy components

* Implement lazy elements / nodes

This is used by Flight to encode not yet resolved nodes of any kind.
2021-04-26 18:46:46 -07:00
Sebastian Markbåge
9cd52b27fe Restore context after an error happens (#21341)
Typically we don't need to restore the context here because we assume that
we'll terminate the rest of the subtree so we don't need the correct
context since we're not rendering any siblings.

However, after a nested suspense boundary we need to restore the context.
The boundary could do this but since we're already doing this in the
suspense branch of renderNode, we might as well do it in the error case
which isn't very perf sensitive anyway.
2021-04-23 12:24:10 -07:00
Sebastian Markbåge
709f948412 [Fizz] Add FB specific streaming API and build (#21337)
Add FB specific streaming API and build
2021-04-22 16:54:29 -07:00
Ricky
a632f7de3b Flip tuple order of useTransition (#20976) 2021-04-20 12:21:44 -04:00
Sebastian Markbåge
cc4b431dab Mark boundary as client rendered even if aborting fallback (#21294) 2021-04-15 19:16:40 -04:00
Sebastian Markbåge
9eddfbf5af [Fizz] Two More Fixes (#21288)
* Emit value of option tags

* Mask the legacy context passed to classes
2021-04-15 10:26:49 -07:00
Sebastian Markbåge
11b07597ee Fix classes (#21283) 2021-04-15 08:06:31 -07:00
Sebastian Markbåge
96d00b9bba [Fizz] Random Fixes (#21277) 2021-04-14 23:29:30 -04:00
Sebastian Markbåge
81ef539535 Always insert a dummy node with an ID into fallbacks (#21272) 2021-04-14 15:39:51 -07:00
Sebastian Markbåge
a4a940d7a1 [Fizz] Add unsupported Portal/Scope components (#21261)
* Update Portal error message

* Add Scope Component
2021-04-14 14:35:36 -07:00
Sebastian Markbåge
f4d7a0f1ea Implement useOpaqueIdentifier (#21260)
The format of this ID is specific to the format.
2021-04-14 14:25:42 -07:00
Sebastian Markbåge
dde875dfb1 [Fizz] Implement Hooks (#21257)
* Implement Fizz Hooks

This is pretty much just a copy of the partial renderer Hooks.

* Implement forward ref and memo
2021-04-14 14:16:41 -07:00
Sebastian Markbåge
a597c2f5dc [Fizz] Fix reentrancy bug (#21270)
* Fix reentrancy bug

* Fix another reentrancy bug

There's also an issue if we try to schedule something to be client
rendered if its fallback hasn't rendered yet. So we don't do it
in that case.
2021-04-14 13:49:14 -07:00
Sebastian Markbåge
4f76a28c93 [Fizz] Implement New Context (#21255)
* Add NewContext module

This implements a reverse linked list tree containing the previous
contexts.

* Implement recursive algorithm

This algorithm pops the contexts back to a shared ancestor on the way down
the stack and then pushes new contexts in reverse order up the stack.

* Move isPrimaryRenderer to ServerFormatConfig

This is primarily intended to be used to support renderToString with a
separate build than the main one. This allows them to be nested.

* Wire up more element type matchers

* Wire up Context Provider type

* Wire up Context Consumer

* Test

* Implement reader in class

* Update error codez
2021-04-14 11:45:42 -07:00
Sebastian Markbåge
dbadfa2c36 [Fizz] Classes Follow Up (#21253)
* Port Classes from Fiber to Fizz

* Test
2021-04-13 13:57:36 -07:00
Sebastian Markbåge
343710c923 [Fizz] Fragments and Iterable support (#21228) 2021-04-10 15:50:42 -04:00
Sebastian Markbåge
b0407b55ff Support more empty types (#21225)
Undefined errors as a direct return value.

This changes semantics for "true" and functions to mirror the client.
2021-04-09 16:23:37 -07:00
Sebastian Markbåge
cf45a623a1 [Fizz] Implement Classes (#21200)
* Legacy context

* Port Classes from Fiber to Fizz
2021-04-08 10:42:37 -07:00
Sebastian Markbåge
ad6e6ec7bb [Fizz] Prepare Recursive Loop for More Types (#21186)
* Split out into helper functions

This is similar to the structure of beginWork in Fiber.

* Split the rendering of a node from recursively rendering a node

This lets us reuse render node at the root which doesn't spawn new work.
2021-04-07 11:29:06 -07:00
Sebastian Markbåge
172e89b4bf Reland Remove redundant initial of isArray (#21188)
* Remove redundant initial of isArray (#21163)

* Reapply prettier

* Type the isArray function with refinement support

This ensures that an argument gets refined just like it does if isArray is
used directly.

I'm not sure how to express with just a direct reference so I added a
function wrapper and confirmed that this does get inlined properly by
closure compiler.

* A few more

* Rename unit test to internal

This is not testing a bundle.

Co-authored-by: Behnam Mohammadi <itten@live.com>
2021-04-07 07:57:43 -07:00
Sebastian Markbage
b4f119cdf1 Revert "Remove redundant initial of isArray (#21163)"
This reverts commit b130a0f5cd.
2021-04-01 15:19:00 -04:00
Behnam Mohammadi
b130a0f5cd Remove redundant initial of isArray (#21163) 2021-04-01 10:50:48 -07:00
Sebastian Markbåge
1cf9978d89 Don't pass internals to callbacks (#21161)
I noticed that I accidentally pass the request object to public API callbacks
as "this".
2021-04-01 08:43:12 -07:00
Sebastian Markbåge
b9e4c10e99 [Fizz] Implement all the DOM attributes and special cases (#21153)
* Implement DOM format config structure

* Styles

* Input warnings

* Textarea special cases

* Select special cases

* Option special cases

We read the currently selected value from the FormatContext.

* Warning for non-lower case HTML

We don't change to lower case at runtime anymore but keep the warning.

* Pre tags innerHTML needs to be prefixed

This is because if you do the equivalent on the client using innerHTML,
this is the effect you'd get.

* Extract errors
2021-03-31 17:39:38 -07:00
Sebastian Markbåge
0853aab74d Log all errors to console.error by default (#21130) 2021-03-29 19:39:55 -07:00
Sebastian Markbåge
d1294c9d40 [Flight] Add global onError handler (#21129)
* Add onError option to Flight Server

The callback is called any time an error is generated in a server component.

This allows it to be logged on a server if needed. It'll still be rethrown
on the client so it can be logged there too but in case it never reaches
the client, here's a way to make sure it doesn't get lost.

* Add fatal error handling
2021-03-29 19:36:16 -07:00
Sebastian Markbåge
32d6f39edd [Fizz] Support special HTML/SVG/MathML tags to suspend (#21113)
* Encode tables as a special insertion mode

The table modes are special in that its children can't be created outside
a table context so we need the segment container to be wrapped in a table.

* Move formatContext from Task to Segment

It works the same otherwise. It's just that this context needs to outlive
the task so that I can use it when writing the segment.

* Use template tag for placeholders and inserted dummy nodes with IDs

These can be used in any parent. At least outside IE11. Not sure yet what
happens in IE11 to these.

Not sure if these are bad for perf since they're special nodes.

* Add special wrappers around inserted segments depending on their insertion mode

* Allow the root namespace to be configured

This allows us to insert the correct wrappers when streaming into an
existing non-HTML tree.

* Add comment
2021-03-27 10:50:38 -07:00
Sebastian Markbåge
556644e237 Fix plurals (#21106) 2021-03-25 22:21:41 -04:00
Sebastian Markbåge
8b741437b1 Rename SuspendedWork to Task (#21105) 2021-03-25 18:39:59 -07:00
Sebastian Markbåge
38a1aedb49 [Fizz] Add FormatContext and Refactor Work (#21103)
* Add format context

* Let the Work node hold all working state for the recursive loop

Stacks are nice and all but there's a cost to maintaining each frame
both in terms of stack size usage and writing to it.

* Move current format context into work

* Synchronously render children of a Suspense boundary

We don't have to spawn work and snapshot the context. Instead we can try
to render the boundary immediately in case it works.

* Lazily create the fallback work

Instead of eagerly create the fallback work and then immediately abort it.
We can just avoid creating it if we finish synchronously.
2021-03-25 18:38:43 -07:00
Sebastian Markbåge
435cff9866 [Fizz] Expose callbacks in options for when various stages of the content is done (#21056)
* Report errors to a global handler

This allows you to log errors or set things like status codes.

* Add complete callback

* onReadyToStream callback

This is typically not needed because if you want to stream when the
root is ready you can just start writing immediately.

* Rename onComplete -> onCompleteAll
2021-03-23 11:39:38 -07:00
Sebastian Markbåge
8fe7810e70 Remove already completed comment (#21054) 2021-03-22 15:41:22 -07:00