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

[core] Minify error messages in production #21214

Merged
merged 29 commits into from Jun 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
a2420f2
Implement building
eps1lon May 26, 2020
e2bdf4f
Test errors
eps1lon May 26, 2020
ea02c58
Use path that'll be used in prod
eps1lon May 26, 2020
2df2834
Format
eps1lon May 26, 2020
d23b822
fix lint
eps1lon May 26, 2020
4bfc1aa
Silence lint to unblock dev
eps1lon May 26, 2020
dea33cd
Move macros to dedicated folder
eps1lon May 26, 2020
e2ba20b
Implement error code extraction
eps1lon May 26, 2020
455f8df
Extract errors and verify in CI
eps1lon May 26, 2020
900d415
fix: escape backticks
eps1lon May 26, 2020
34d18af
Scaffold /error-decoder page
eps1lon May 26, 2020
88ddb9d
yarn deduplicate
eps1lon May 26, 2020
b7fea6e
fix: Provide cooked template literal value
eps1lon May 28, 2020
776da74
More documentation for templateElements
eps1lon May 28, 2020
5f0fe5b
Shorten message. We can add more hints to the decoder page
eps1lon May 28, 2020
b05c141
Reduce transpilation junk
eps1lon May 29, 2020
0857f5b
fix: Use a single import
eps1lon May 29, 2020
e5bf6ec
fix: pre-transpile template literals in loose mode
eps1lon May 29, 2020
cb154de
Add test for unsupported callexpression
eps1lon May 29, 2020
79efd56
fix: ignore ErrorDecoder in visual regression tests
eps1lon May 29, 2020
4208903
fix: webpack can't tree-shake a "mixed-barell"
eps1lon May 29, 2020
8c38193
Minify errors in prod where possible
eps1lon May 29, 2020
9763a5f
Implement page
eps1lon May 30, 2020
6cda25b
error-decoder -> production-error
eps1lon May 30, 2020
75bdd02
nextjs does not support fallbacks for export because?
eps1lon May 30, 2020
b72b10d
Update test/regressions/index.js
eps1lon Jun 2, 2020
583982d
Update packages/material-ui-utils/macros/__fixtures__/.eslintrc.js
eps1lon Jun 3, 2020
0e82238
Inline evaluateStringAST
eps1lon Jun 2, 2020
f3058db
Merge branch 'master' into feat/minify-errors
eps1lon Jun 3, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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