Skip to content

Commit

Permalink
[core] Minify error messages in production (#21214)
Browse files Browse the repository at this point in the history
  • Loading branch information
eps1lon committed Jun 3, 2020
1 parent 5e09d42 commit 3a7d4c6
Show file tree
Hide file tree
Showing 40 changed files with 640 additions and 55 deletions.
5 changes: 5 additions & 0 deletions .circleci/config.yml
Expand Up @@ -124,6 +124,11 @@ jobs:
- run:
name: Lint
command: yarn lint:ci
- run:
name: '`yarn extract-error-codes` changes committed?'
command: |
yarn extract-error-codes
git diff --exit-code
- run:
name: Lint JSON
command: yarn --silent jsonlint
Expand Down
6 changes: 5 additions & 1 deletion .eslintrc.js
Expand Up @@ -44,7 +44,11 @@ module.exports = {
'no-restricted-imports': [
'error',
{
patterns: ['@material-ui/*/*/*', '!@material-ui/core/test-utils/*'],
patterns: [
'@material-ui/*/*/*',
'!@material-ui/core/test-utils/*',
'!@material-ui/utils/macros/*.macro',
],
},
],
'nonblock-statement-body-position': 'error',
Expand Down
14 changes: 14 additions & 0 deletions babel.config.js
@@ -1,3 +1,8 @@
const path = require('path');

const errorCodesPath = path.resolve(__dirname, './docs/public/static/error-codes.json');
const missingError = process.env.MUI_EXTRACT_ERROR_CODES === 'true' ? 'write' : 'annotate';

let defaultPresets;

// We release a ES version of Material-UI.
Expand Down Expand Up @@ -42,6 +47,15 @@ const productionPlugins = [
module.exports = {
presets: defaultPresets.concat(['@babel/preset-react', '@babel/preset-typescript']),
plugins: [
[
'babel-plugin-macros',
{
muiError: {
errorCodesPath,
missingError,
},
},
],
'babel-plugin-optimize-clsx',
['@babel/plugin-proposal-class-properties', { loose: true }],
['@babel/plugin-proposal-object-rest-spread', { loose: true }],
Expand Down
11 changes: 11 additions & 0 deletions docs/babel.config.js
@@ -1,5 +1,8 @@
const bpmr = require('babel-plugin-module-resolver');
const fse = require('fs-extra');
const path = require('path');

const errorCodesPath = path.resolve(__dirname, './public/static/error-codes.json');

function resolvePath(sourcePath, currentFile, opts) {
if (sourcePath === 'markdown') {
Expand Down Expand Up @@ -33,6 +36,14 @@ module.exports = {
['next/babel', { 'transform-runtime': { corejs: 2, version: transformRuntimeVersion } }],
],
plugins: [
[
'babel-plugin-macros',
{
muiError: {
errorCodesPath,
},
},
],
'babel-plugin-optimize-clsx',
// for IE 11 support
'@babel/plugin-transform-object-assign',
Expand Down
20 changes: 20 additions & 0 deletions docs/pages/production-error.js
@@ -0,0 +1,20 @@
import React from 'react';
import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
import { prepareMarkdown } from 'docs/src/modules/utils/parseMarkdown';

const pageFilename = 'production-error';
const requireDemo = require.context('docs/src/pages/production-error', false, /\.js$/);
const requireRaw = require.context(
'!raw-loader!../src/pages/production-error',
false,
/\.(js|md)$/,
);

export default function Page({ demos, docs }) {
return <MarkdownDocs demos={demos} disableAd docs={docs} requireDemo={requireDemo} />;
}

Page.getInitialProps = () => {
const { demos, docs } = prepareMarkdown({ pageFilename, requireRaw });
return { demos, docs };
};
9 changes: 9 additions & 0 deletions docs/public/static/error-codes.json
@@ -0,0 +1,9 @@
{
"1": "Material-UI: Expected valid input target. Did you use a custom `inputComponent` and forget to forward refs? See https://material-ui.com/r/input-component-ref-interface for more info.",
"2": "Material-UI: The `value` prop must be an array when using the `Select` component with `multiple`.",
"3": "Material-UI: Unsupported `%s` color.\nWe support the following formats: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla().",
"4": "Material-UI: The color provided to augmentColor(color) is invalid.\nThe color object needs to have a `main` property or a `%s` property.",
"5": "Material-UI: The color provided to augmentColor(color) is invalid.\n`color.main` should be a string, but `%s` was provided instead.\n\nDid you intend to use one of the following approaches?\n\nimport { green } from \"@material-ui/core/colors\";\n\nconst theme1 = createMuiTheme({ palette: {\n primary: green,\n} });\n\nconst theme2 = createMuiTheme({ palette: {\n primary: { main: green[500] },\n} });",
"6": "Material-UI: Unsupported non-unitless line height with grid alignment.\nUse unitless line heights instead.",
"7": "Material-UI: capitalize(string) expects a string argument."
}
2 changes: 1 addition & 1 deletion docs/src/modules/components/AppFrame.js
Expand Up @@ -188,7 +188,7 @@ function AppFrame(props) {
let navIconClassName = '';
let appBarClassName = classes.appBar;

if (!activePage || activePage.disableDrawer === true) {
if (activePage?.disableDrawer === true) {
disablePermanent = true;
appBarClassName += ` ${classes.appBarHome}`;
} else {
Expand Down
4 changes: 2 additions & 2 deletions docs/src/modules/components/MarkdownDocs.js
Expand Up @@ -89,7 +89,7 @@ const styles = (theme) => ({
});

function MarkdownDocs(props) {
const { classes, disableAd = false, disableToc = false, demos, docs, requireDemo } = props;
const { classes, disableAd = false, disableToc = false, demos = {}, docs, requireDemo } = props;

const t = useSelector((state) => state.options.t);
const userLanguage = useSelector((state) => state.options.userLanguage);
Expand All @@ -100,7 +100,7 @@ function MarkdownDocs(props) {

const { activePage, pages } = React.useContext(PageContext);
const pageList = flattenPages(pages);
const currentPageNum = findIndex(pageList, (page) => page.pathname === activePage.pathname);
const currentPageNum = findIndex(pageList, (page) => page.pathname === activePage?.pathname);
const currentPage = pageList[currentPageNum];
const prevPage = pageList[currentPageNum - 1];
const nextPage = pageList[currentPageNum + 1];
Expand Down
126 changes: 126 additions & 0 deletions docs/src/pages/production-error/ErrorDecoder.js
@@ -0,0 +1,126 @@
import * as React from 'react';
import { useRouter } from 'next/router';
import Link from '@material-ui/core/Link';
import Typography from '@material-ui/core/Typography';
import { styled } from '@material-ui/core/styles';
import MarkdownElement from 'docs/src/modules/components/MarkdownElement';
import { render as renderMarkdown } from 'docs/src/modules/utils/parseMarkdown';

const ErrorMessageSection = styled('div')({
// reset display: block from Demo
display: 'block',
});

// use elevation={2}
const ErrorMessageMarkdown = styled(MarkdownElement)(({ theme }) => {
return {
boxShadow: theme.shadows['2'],
color: theme.palette.error.main,
padding: theme.spacing(1, 2),
};
});

export default function ErrorDecoder() {
const {
query: { code, ...query },
} = useRouter();
const queryArgs = query['args[]'];
const args = React.useMemo(() => (Array.isArray(queryArgs) ? queryArgs : [queryArgs]), [
queryArgs,
]);

const [data, dispatch] = React.useReducer(
(previousState, action) => {
switch (action.type) {
case 'rejected':
return { errorCodes: null, state: 'rejected' };
case 'resolved':
return { errorCodes: action.payload, state: 'resolved' };
default:
throw new Error(`We made a mistake passing an unknown action.`);
}
},
{ errorCodes: null, state: 'loading' },
);

React.useEffect(() => {
let cancelled = false;

fetch('/static/error-codes.json')
.then((response) => {
return response.json();
})
.then((json) => {
if (cancelled === false) {
dispatch({ type: 'resolved', payload: json });
}
})
.catch(() => {
dispatch({ type: 'rejected' });
});

return () => {
cancelled = true;
};
}, []);

const errorMessage = React.useMemo(() => {
const rawMessage = data.errorCodes?.[code];
if (rawMessage === undefined) {
return undefined;
}

let replacementIndex = -1;
const readableMessage = rawMessage.replace(/%s/g, () => {
replacementIndex += 1;
const dangerousArgument = args[replacementIndex];
if (dangerousArgument === undefined) {
return '[missing argument]';
}
// String will be injected into innerHTML.
// We need to escape first
// https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML#Security_considerations
const div = document.createElement('div');
div.innerText = dangerousArgument;
return div.innerHTML;
});

return renderMarkdown(readableMessage);
}, [args, code, data.errorCodes]);

if (data.state === 'loading') {
return <Typography>Loading error codes</Typography>;
}

if (data.state === 'rejected') {
return (
<Typography color="error">
Seems like we&apos;re having some issues loading the original message. Try reloading the
page. If the error persists please report this isse on our{' '}
<Link
href="https://github.com/mui-org/material-ui/issues/new?template=1.bug.md"
target="_blank"
>
issue tracker
</Link>
.
</Typography>
);
}

if (errorMessage === undefined) {
return (
<Typography>
When you encounter an error, you&apos;ll receive a link to this page for that specific error
and we&apos;ll show you the full error text.
</Typography>
);
}

return (
<ErrorMessageSection>
<p>The original text of the error you encountered:</p>
<ErrorMessageMarkdown renderedMarkdown={errorMessage} />
</ErrorMessageSection>
);
}
9 changes: 9 additions & 0 deletions docs/src/pages/production-error/index.md
@@ -0,0 +1,9 @@
# Production Error

<p class="description">In the production build of Material-UI error messages are minified to reduce the size of your application.</p>

We recommend using the development build when debugging this error.
It will include additional warnings about potential problems.
If you encounter an exception while using the production build, this page will reassemble the orrigianl text of the error.

{{"demo": "pages/production-error/ErrorDecoder.js", "hideToolbar": true, "bg": "inline"}}
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -21,6 +21,7 @@
"docs:typescript:check": "yarn workspace docs typescript",
"docs:typescript:formatted": "yarn workspace docs typescript:transpile",
"docs:mdicons:synonyms": "babel-node --config-file ./babel.config.js ./docs/scripts/updateIconSynonyms",
"extract-error-codes": "lerna run --parallel extract-error-codes",
"framer:build": "yarn workspace framer build",
"jsonlint": "node scripts/jsonlint.js",
"lint": "eslint . --cache --report-unused-disable-directives",
Expand Down Expand Up @@ -74,6 +75,7 @@
"babel-eslint": "^10.0.3",
"babel-loader": "^8.0.0",
"babel-plugin-istanbul": "^6.0.0",
"babel-plugin-macros": "^2.8.0",
"babel-plugin-module-resolver": "^4.0.0",
"babel-plugin-optimize-clsx": "^2.3.0",
"babel-plugin-react-remove-properties": "^0.3.0",
Expand Down

0 comments on commit 3a7d4c6

Please sign in to comment.