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

Update error minification script to support Error call expressions #22428

Closed
wants to merge 3 commits into from

Conversation

acdlite
Copy link
Collaborator

@acdlite acdlite commented Sep 25, 2021

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.

Test plan

I added Jest tests for the Babel transform.

There was only a single place in our codebase where we used Error instead of invariant, in throwException. I assigned it an error code and confirmed it's now being minified.

Before:

value = Error(
  (getComponentNameFromFiber(sourceFiber) || "A React component") +
    " suspended while rendering, but no fallback UI was specified.\n\nAdd a <Suspense fallback=...> component higher in the tree to provide a loading indicator or placeholder to display."
);

After:

value = Error(
  formatProdErrorMessage(
    409,
    getComponentNameFromFiber(sourceFiber) || "A React component"
  )
);

Note: we should also strip all uses of getComponentNameFromFiber from the production build, but we'll fix that separately.

@facebook-github-bot facebook-github-bot added the React Core Team Opened by a member of the React Core Team label Sep 25, 2021
@sizebot
Copy link

sizebot commented Sep 25, 2021

Comparing: 7d38e4f...2bcea20

Critical size changes

Includes critical production bundles, as well as any change greater than 2%:

Name +/- Base Current +/- gzip Base gzip Current gzip
oss-stable/react-dom/cjs/react-dom.production.min.js = 130.22 kB 130.04 kB = 41.45 kB 41.33 kB
oss-experimental/react-dom/cjs/react-dom.production.min.js = 133.05 kB 132.87 kB = 42.42 kB 42.30 kB
facebook-www/ReactDOM-prod.classic.js = 412.61 kB 412.48 kB = 76.37 kB 76.26 kB
facebook-www/ReactDOM-prod.modern.js = 401.23 kB 401.11 kB = 74.67 kB 74.55 kB
facebook-www/ReactDOMForked-prod.classic.js = 412.61 kB 412.48 kB = 76.38 kB 76.27 kB
oss-experimental/react-fetch/cjs/react-fetch.node.production.min.js +15.18% 2.01 kB 2.31 kB +18.95% 0.91 kB 1.09 kB
oss-stable-semver/react-fetch/cjs/react-fetch.node.production.min.js +15.18% 2.01 kB 2.31 kB +18.95% 0.91 kB 1.09 kB
oss-stable/react-fetch/cjs/react-fetch.node.production.min.js +15.18% 2.01 kB 2.31 kB +18.95% 0.91 kB 1.09 kB
oss-experimental/jest-react/cjs/jest-react.production.min.js = 2.62 kB 2.40 kB = 1.30 kB 1.18 kB
oss-stable-semver/jest-react/cjs/jest-react.production.min.js = 2.62 kB 2.40 kB = 1.30 kB 1.18 kB
oss-stable/jest-react/cjs/jest-react.production.min.js = 2.62 kB 2.40 kB = 1.30 kB 1.18 kB

Significant size changes

Includes any change greater than 0.2%:

Expand to show
Name +/- Base Current +/- gzip Base gzip Current gzip
oss-experimental/react-fetch/cjs/react-fetch.node.production.min.js +15.18% 2.01 kB 2.31 kB +18.95% 0.91 kB 1.09 kB
oss-stable-semver/react-fetch/cjs/react-fetch.node.production.min.js +15.18% 2.01 kB 2.31 kB +18.95% 0.91 kB 1.09 kB
oss-stable/react-fetch/cjs/react-fetch.node.production.min.js +15.18% 2.01 kB 2.31 kB +18.95% 0.91 kB 1.09 kB
oss-experimental/react-art/cjs/react-art.production.min.js = 85.47 kB 85.29 kB = 26.49 kB 26.37 kB
oss-stable-semver/react-art/cjs/react-art.production.min.js = 83.08 kB 82.90 kB = 25.70 kB 25.58 kB
oss-stable/react-art/cjs/react-art.production.min.js = 83.08 kB 82.90 kB = 25.70 kB 25.58 kB
oss-experimental/react-test-renderer/cjs/react-test-renderer.production.min.js = 82.24 kB 82.06 kB = 25.42 kB 25.30 kB
oss-experimental/react-test-renderer/umd/react-test-renderer.production.min.js = 82.43 kB 82.25 kB = 25.84 kB 25.72 kB
oss-stable-semver/react-test-renderer/umd/react-test-renderer.production.min.js = 80.05 kB 79.87 kB = 24.99 kB 24.88 kB
oss-stable/react-test-renderer/umd/react-test-renderer.production.min.js = 80.05 kB 79.87 kB = 24.99 kB 24.88 kB
oss-stable-semver/react-test-renderer/cjs/react-test-renderer.production.min.js = 79.86 kB 79.68 kB = 24.67 kB 24.56 kB
oss-stable/react-test-renderer/cjs/react-test-renderer.production.min.js = 79.86 kB 79.68 kB = 24.67 kB 24.56 kB
oss-experimental/jest-react/cjs/jest-react.production.min.js = 2.62 kB 2.40 kB = 1.30 kB 1.18 kB
oss-stable-semver/jest-react/cjs/jest-react.production.min.js = 2.62 kB 2.40 kB = 1.30 kB 1.18 kB
oss-stable/jest-react/cjs/jest-react.production.min.js = 2.62 kB 2.40 kB = 1.30 kB 1.18 kB

Generated by 🚫 dangerJS against 2bcea20

@acdlite acdlite force-pushed the minify-error-messages branch from 150afa1 to 599e1a1 Compare September 25, 2021 21:26
@acdlite acdlite force-pushed the minify-error-messages branch 5 times, most recently from 495df2f to b41dc1c Compare September 25, 2021 21:58
@@ -287,12 +287,10 @@
"288": "It is not supported to run the profiling version of a renderer (for example, `react-dom/profiling`) without also replacing the `schedule/tracing` module with `schedule/tracing-profiling`. Your bundler might have a setting for aliasing both modules. Learn more at https://reactjs.org/link/profiling",
"289": "Function components cannot have refs.",
"290": "Element ref was specified as a string (%s) but no owner was set. This could happen for one of the following reasons:\n1. You may be adding a ref to a function component\n2. You may be adding a ref to a component that was not created inside a component's render method\n3. You have multiple copies of React loaded\nSee https://reactjs.org/link/refs-must-have-owner for more information.",
"291": "Log of yielded values is not empty. Call expect(Scheduler).toHaveYielded(...) first.",
Copy link
Collaborator Author

@acdlite acdlite Sep 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See https://github.com/facebook/react/pull/22428/files#r716105083. Since this message was never part of a public bundle we can remove it.

"292": "The matcher `toHaveYielded` expects an instance of React Test Renderer.\n\nTry: expect(Scheduler).toHaveYielded(expectedYields)",
"293": "Context can only be read while React is rendering, e.g. inside the render method or getDerivedStateFromProps.",
"294": "ReactDOMServer does not yet support Suspense.",
"295": "ReactDOMServer does not yet support lazy-loaded components.",
"296": "Log of yielded values is not empty. Call expect(ReactTestRenderer).unstable_toHaveYielded(...) first.",
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

@acdlite acdlite force-pushed the minify-error-messages branch from b41dc1c to c09d87d Compare September 25, 2021 22:04
@acdlite
Copy link
Collaborator Author

acdlite commented Sep 25, 2021

Ironically the server build of React Fetch increased slightly in size because this message started being minified:

throw new Error('Not implemented.');

but since it's the only error message in that package, the cost of the formatProdErrorMessage function is not amortized.

@acdlite acdlite force-pushed the minify-error-messages branch from c09d87d to d0b70c9 Compare September 25, 2021 22:09
'Log of yielded values is not empty. ' +
'Call expect(ReactTestRenderer).unstable_toHaveYielded(...) first.',
);
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noticed this was being minified in jest-react but that's an internal module so there's no reason to. The extract-errors script must have picked this up at some point. Since it's not part of a public bundle we don't have to minify it.

@acdlite acdlite marked this pull request as ready for review September 25, 2021 22:24
@acdlite acdlite force-pushed the minify-error-messages branch from d0b70c9 to 7553658 Compare September 26, 2021 19:10
@acdlite acdlite closed this Sep 27, 2021
@acdlite acdlite force-pushed the minify-error-messages branch from 7553658 to 05726d7 Compare September 27, 2021 17:06
@acdlite acdlite reopened this Sep 27, 2021
@acdlite acdlite force-pushed the minify-error-messages branch from bba6bb8 to 45a60e0 Compare September 27, 2021 17:51
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.
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.
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.
@acdlite acdlite force-pushed the minify-error-messages branch from 45a60e0 to 2bcea20 Compare September 30, 2021 15:28
@acdlite acdlite closed this Sep 30, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed React Core Team Opened by a member of the React Core Team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants