Commit Graph

13 Commits

Author SHA1 Message Date
Sebastian Markbåge
151cce3740
Track Stack of JSX Calls (#29032)
This is the first step to experimenting with a new type of stack traces
behind the `enableOwnerStacks` flag - in DEV only.

The idea is to generate stacks that are more like if the JSX was a
direct call even though it's actually a lazy call. Not only can you see
which exact JSX call line number generated the erroring component but if
that's inside an abstraction function, which function called that
function and if it's a component, which component generated that
component. For this to make sense it really need to be the "owner" stack
rather than the parent stack like we do for other component stacks. On
one hand it has more precise information but on the other hand it also
loses context. For most types of problems the owner stack is the most
useful though since it tells you which component rendered this
component.

The problem with the platform in its current state is that there's two
ways to deal with stacks:

1) `new Error().stack` 
2) `console.createTask()`

The nice thing about `new Error().stack` is that we can extract the
frames and piece them together in whatever way we want. That is great
for constructing custom UIs like error dialogs. Unfortunately, we can't
take custom stacks and set them in the native UIs like Chrome DevTools.

The nice thing about `console.createTask()` is that the resulting stacks
are natively integrated into the Chrome DevTools in the console and the
breakpoint debugger. They also automatically follow source mapping and
ignoreLists. The downside is that there's no way to extract the async
stack outside the native UI itself so this information cannot be used
for custom UIs like errors dialogs. It also means we can't collect this
on the server and then pass it to the client for server components.

The solution here is that we use both techniques and collect both an
`Error` object and a `Task` object for every JSX call.

The main concern about this approach is the performance so that's the
main thing to test. It's certainly too slow for production but it might
also be too slow even for DEV.

This first PR doesn't actually use the stacks yet. It just collects them
as the first step. The next step is to start utilizing this information
in error printing etc.

For RSC we pass the stack along across over the wire. This can be
concatenated on the client following the owner path to create an owner
stack leading back into the server. We'll later use this information to
restore fake frames on the client for native integration. Since this
information quickly gets pretty heavy if we include all frames, we strip
out the top frame. We also strip out everything below the functions that
call into user space in the Flight runtime. To do this we need to figure
out the frames that represents calling out into user space. The
resulting stack is typically just the one frame inside the owner
component's JSX callsite. I also eagerly strip out things we expect to
be ignoreList:ed anyway - such as `node_modules` and Node.js internals.
2024-05-09 12:23:05 -04:00
Sebastian Markbåge
6090cab099
Use a Wrapper Error for onRecoverableError with a "cause" Field for the real Error (#28736)
We basically have four kinds of recoverable errors:

- Hydration mismatches.
- Server errored but client didn't.
- Hydration render errored but client render didn't (in Root or Suspense
boundary).
- Concurrent render errored but synchronous render didn't.

For the first three we log an additional error that the root or Suspense
boundary didn't error. This provides some context about what happened.
However, the problem is that for hydration mismatches that's unnecessary
extra context that is confusing. We also don't log any additional
context for concurrent render errors that could recover. This used to be
the only recoverable error so it didn't need extra context but now we
need to distinguish them. When we log these to `reportError` it's
confusing to just see the error because you didn't see anything error on
the page. It's also hard to group them together as one.

In this PR, I remove the unnecessary context for hydration mismatches.

For hydration and concurrent errors, I now wrap them in an error that
describes that what happened but then use the new `cause` field to link
the original error so we can keep that as the cause. The error that
happened was that hydration client rendered or you deopted to sync
render, the cause of that error is some other error.

For server errors, we control the Error object so I already had added
some context to that error object's message. Since we hide the message
in prod, it's nice not to have the raw message in DEV neither. We could
potentially split these into two errors for parity though.
2024-04-03 21:53:07 -04:00
Jan Kassens
6b30832666
Upgrade prettier (#26081)
The old version of prettier we were using didn't support the Flow syntax
to access properties in a type using `SomeType['prop']`. This updates
`prettier` and `rollup-plugin-prettier` to the latest versions.

I added the prettier config `arrowParens: "avoid"` to reduce the diff
size as the default has changed in Prettier 2.0. The largest amount of
changes comes from function expressions now having a space. This doesn't
have an option to preserve the old behavior, so we have to update this.
2023-01-31 08:25:05 -05:00
Andrew Clark
9cdf8a99ed
[Codemod] Update copyright header to Meta (#25315)
* Facebook -> Meta in copyright

rg --files | xargs sed -i 's#Copyright (c) Facebook, Inc. and its affiliates.#Copyright (c) Meta Platforms, Inc. and affiliates.#g'

* Manual tweaks
2022-10-18 11:19:24 -04:00
Josh Story
9e3b772b8c
Update error transform to allow excluding errors inside subexpressions like ternaries (#24693)
* Update error transform to allow excluding errors inside subexpressions like ternaries

* make leadingcomments aggregation walk the expression stack
2022-06-08 16:59:49 -07:00
Andrew Clark
7034408ff7
Follow-up improvements to error code extraction infra (#22516)
* Output FIXME during build for unminified errors

The invariant Babel transform used to output a FIXME comment if it
could not find a matching error code. This could happen if there were
a configuration mistake that caused an unminified message to
slip through.

Linting the compiled bundles is the most reliable way to do it because
there's not a one-to-one mapping between source modules and bundles. For
example, the same source module may appear in multiple bundles, some
which are minified and others which aren't.

This updates the transform to output the same messages for Error calls.

The source lint rule is still useful for catching mistakes during
development, to prompt you to update the error codes map before pushing
the PR to CI.

* Don't run error transform in development

We used to run the error transform in both production and development,
because in development it was used to convert `invariant` calls into
throw statements.

Now that don't use `invariant` anymore, we only have to run the
transform for production builds.

* Add ! to FIXME comment so Closure doesn't strip it

Don't love this solution because Closure could change this heuristic,
or we could switch to a differnt compiler that doesn't support it. But
it works.

Could add a bundle that contains an unminified error solely for the
purpose of testing it, but that seems like overkill.

* Alternate extract-errors that scrapes artifacts

The build script outputs a special FIXME comment when it fails to minify
an error message. CI will detect these comments and fail the workflow.

The comments also include the expected error message. So I added an
alternate extract-errors that scrapes unminified messages from the
build artifacts and updates `codes.json`.

This is nice because it works on partial builds. And you can also run it
after the fact, instead of needing build all over again.

* Disable error minification in more bundles

Not worth it because the number of errors does not outweight the size
of the formatProdErrorMessage runtime.

* Run extract-errors script in CI

The lint_build job already checks for unminified errors, but the output
isn't super helpful.

Instead I've added a new job that runs the extract-errors script and
fails the build if `codes.json` changes. It also outputs the expected
diff so you can easily see which messages were missing from the map.

* Replace old extract-errors script with new one

Deletes the old extract-errors in favor of extract-errors2
2021-10-31 15:37:32 -07:00
Andrew Clark
a724a3b578
[RFC] Codemod invariant -> throw new Error (#22435)
* Hoist error codes import to module scope

When this code was written, the error codes map (`codes.json`) was
created on-the-fly, so we had to lazily require from inside the visitor.

Because `codes.json` is now checked into source, we can import it a
single time in module scope.

* Minify error constructors in production

We use a script to minify our error messages in production. Each message
is assigned an error code, defined in `scripts/error-codes/codes.json`.
Then our build script replaces the messages with a link to our
error decoder page, e.g. https://reactjs.org/docs/error-decoder.html/?invariant=92

This enables us to write helpful error messages without increasing the
bundle size.

Right now, the script only works for `invariant` calls. It does not work
if you throw an Error object. This is an old Facebookism that we don't
really need, other than the fact that our error minification script
relies on it.

So, I've updated the script to minify error constructors, too:

Input:
  Error(`A ${adj} message that contains ${noun}`);
Output:
  Error(formatProdErrorMessage(ERR_CODE, adj, noun));

It only works for constructors that are literally named Error, though we
could add support for other names, too.

As a next step, I will add a lint rule to enforce that errors written
this way must have a corresponding error code.

* Minify "no fallback UI specified" error in prod

This error message wasn't being minified because it doesn't use
invariant. The reason it didn't use invariant is because this particular
error is created without begin thrown — it doesn't need to be thrown
because it's located inside the error handling part of the runtime.

Now that the error minification script supports Error constructors, we
can minify it by assigning it a production error code in
`scripts/error-codes/codes.json`.

To support the use of Error constructors more generally, I will add a
lint rule that enforces each message has a corresponding error code.

* Lint rule to detect unminified errors

Adds a lint rule that detects when an Error constructor is used without
a corresponding production error code.

We already have this for `invariant`, but not for regular errors, i.e.
`throw new Error(msg)`. There's also nothing that enforces the use of
`invariant` besides convention.

There are some packages where we don't care to minify errors. These are
packages that run in environments where bundle size is not a concern,
like react-pg. I added an override in the ESLint config to ignore these.

* Temporarily add invariant codemod script

I'm adding this codemod to the repo temporarily, but I'll revert it
in the same PR. That way we don't have to check it in but it's still
accessible (via the PR) if we need it later.

* [Automated] Codemod invariant -> Error

This commit contains only automated changes:

npx jscodeshift -t scripts/codemod-invariant.js packages --ignore-pattern="node_modules/**/*"
yarn linc --fix
yarn prettier

I will do any manual touch ups in separate commits so they're easier
to review.

* Remove temporary codemod script

This reverts the codemod script and ESLint config I added temporarily
in order to perform the invariant codemod.

* Manual touch ups

A few manual changes I made after the codemod ran.

* Enable error code transform per package

Currently we're not consistent about which packages should have their
errors minified in production and which ones should.

This adds a field to the bundle configuration to control whether to
apply the transform. We should decide what the criteria is going
forward. I think it's probably a good idea to minify any package that
gets sent over the network. So yes to modules that run in the browser,
and no to modules that run on the server and during development only.
2021-09-30 12:01:28 -07:00
Andrew Clark
75955bf1d7
Pass prod error messages directly to constructor (#17063)
* Remove "Invariant Violation" from dev errors

When I made the change to compile `invariant` to throw expressions, I
left a small runtime to set the error's `name` property to "Invariant
Violation" to maintain the existing behavior.

I think we can remove it. The argument for keeping it is to preserve
continuity in error logs, but this only affects development errors,
anyway: production error messages are replaced with error codes.

* Pass prod error messages directly to constructor

Updates the `invariant` transform to pass an error message string
directly to the Error constructor, instead of mutating the
message property.

Turns this code:

```js
invariant(condition, 'A %s message that contains %s', adj, noun);
```

into this:

```js
if (!condition) {
  throw Error(
    __DEV__
      ? `A ${adj} message that contains ${noun}`
      : formatProdErrorMessage(ERR_CODE, adj, noun)
  );
}
```
2019-10-11 09:10:40 -07:00
Andrew Clark
05dc814cf0
Remove IIFE wrappers from dev invariant checks (#16963)
The error transform works by replacing calls to `invariant` with
an `if` statement.

Since we're replacing a call expression with a statement, Babel wraps
the new statement in an immediately-invoked function expression (IIFE).
This wrapper is unnecessary in practice because our `invariant` calls
are always part of their own expression statement.

In the production bundle, the function wrappers are removed by Closure.
But they remain in the development bundles.

This commit updates the transform to confirm that an `invariant` call
expression's parent node is an expression statement. (If not, it throws
a transform error.)

Then, it replaces the expression statement instead of the expression
itself, effectively removing the extraneous IIFE wrapper.
2019-09-30 11:14:51 -07:00
lunaruan
b12a982062
Babel 7 (#16297)
Upgraded from Babel 6 to Babel 7.

The only significant change seems to be the way `@babel/plugin-transform-classes` handles classes differently from `babel-plugin-transform-es2015-classes`. In regular mode, the former injects a `_createClass` function that increases the bundle size, and in the latter it removes the safeguard checks. However, this is okay because we don't all classes in new features, and we want to deprecate class usage in the future in the react repo.

Co-authored-by: Luna Ruan <luna@fb.com>
Co-authored-by: Abdul Rauf <abdulraufmujahid@gmail.com>
Co-authored-by: Maksim Markelov <maks-markel@mail.ru>
2019-08-08 17:46:35 -07:00
Moti Zilberman
98454371a9 Construct Error at invariant call site for clearer stack traces (#15877) 2019-06-18 11:38:33 -07:00
Andrew Clark
30b1a8009c
Don't extract errors in CI (#15758)
Removes `--extract-errors` argument from CI build script command.
Instead, the author is expected to run `yarn extract-errors` locally
or manually edit the error code map.

The lint rule should be sufficient to catch unminified errors, but
as an extra precaution, I added a post-build step that greps the
production bundles. The post-build step works even if someone disables
the lint rule for a specific line or file.
2019-05-29 14:20:29 -07:00
Andrew Clark
875d05d553
Include full error messages in React Native build (#15363)
The React Native build does not minify error messages in production,
but it still needs to run the error messages transform to compile
`invariant` calls to `ReactError`. To do this, I added a `noMinify`
option to the Babel plugin. I also renamed it from
`minify-error-messages` to the more generic `transform-error-messages`.
2019-04-09 16:40:19 -07:00