mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
* 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
134 lines
4.0 KiB
JavaScript
134 lines
4.0 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
'use strict';
|
|
|
|
const fs = require('fs');
|
|
const {evalStringAndTemplateConcat} = require('../shared/evalToString');
|
|
const invertObject = require('./invertObject');
|
|
const helperModuleImports = require('@babel/helper-module-imports');
|
|
|
|
const errorMap = invertObject(
|
|
JSON.parse(fs.readFileSync(__dirname + '/codes.json', 'utf-8'))
|
|
);
|
|
|
|
const SEEN_SYMBOL = Symbol('transform-error-messages.seen');
|
|
|
|
module.exports = function(babel) {
|
|
const t = babel.types;
|
|
|
|
function ErrorCallExpression(path, file) {
|
|
// Turns this code:
|
|
//
|
|
// new Error(`A ${adj} message that contains ${noun}`);
|
|
//
|
|
// or this code (no constructor):
|
|
//
|
|
// Error(`A ${adj} message that contains ${noun}`);
|
|
//
|
|
// into this:
|
|
//
|
|
// Error(formatProdErrorMessage(ERR_CODE, adj, noun));
|
|
const node = path.node;
|
|
if (node[SEEN_SYMBOL]) {
|
|
return;
|
|
}
|
|
node[SEEN_SYMBOL] = true;
|
|
|
|
const errorMsgNode = node.arguments[0];
|
|
if (errorMsgNode === undefined) {
|
|
return;
|
|
}
|
|
|
|
const errorMsgExpressions = [];
|
|
const errorMsgLiteral = evalStringAndTemplateConcat(
|
|
errorMsgNode,
|
|
errorMsgExpressions
|
|
);
|
|
|
|
let prodErrorId = errorMap[errorMsgLiteral];
|
|
if (prodErrorId === undefined) {
|
|
// There is no error code for this message. Add an inline comment
|
|
// that flags this as an unminified error. This allows the build
|
|
// to proceed, while also allowing a post-build linter to detect it.
|
|
//
|
|
// Outputs:
|
|
// /* FIXME (minify-errors-in-prod): Unminified error message in production build! */
|
|
// /* <expected-error-format>"A % message that contains %"</expected-error-format> */
|
|
// if (!condition) {
|
|
// throw Error(`A ${adj} message that contains ${noun}`);
|
|
// }
|
|
|
|
const statementParent = path.getStatementParent();
|
|
const leadingComments = statementParent.node.leadingComments;
|
|
if (leadingComments !== undefined) {
|
|
for (let i = 0; i < leadingComments.length; i++) {
|
|
// TODO: Since this only detects one of many ways to disable a lint
|
|
// rule, we should instead search for a custom directive (like
|
|
// no-minify-errors) instead of ESLint. Will need to update our lint
|
|
// rule to recognize the same directive.
|
|
const commentText = leadingComments[i].value;
|
|
if (
|
|
commentText.includes(
|
|
'eslint-disable-next-line react-internal/prod-error-codes'
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
statementParent.addComment(
|
|
'leading',
|
|
`! <expected-error-format>"${errorMsgLiteral}"</expected-error-format>`
|
|
);
|
|
statementParent.addComment(
|
|
'leading',
|
|
'! FIXME (minify-errors-in-prod): Unminified error message in production build!'
|
|
);
|
|
return;
|
|
}
|
|
prodErrorId = parseInt(prodErrorId, 10);
|
|
|
|
// Import formatProdErrorMessage
|
|
const formatProdErrorMessageIdentifier = helperModuleImports.addDefault(
|
|
path,
|
|
'shared/formatProdErrorMessage',
|
|
{nameHint: 'formatProdErrorMessage'}
|
|
);
|
|
|
|
// Outputs:
|
|
// formatProdErrorMessage(ERR_CODE, adj, noun);
|
|
const prodMessage = t.callExpression(formatProdErrorMessageIdentifier, [
|
|
t.numericLiteral(prodErrorId),
|
|
...errorMsgExpressions,
|
|
]);
|
|
|
|
// Outputs:
|
|
// Error(formatProdErrorMessage(ERR_CODE, adj, noun));
|
|
const newErrorCall = t.callExpression(t.identifier('Error'), [prodMessage]);
|
|
newErrorCall[SEEN_SYMBOL] = true;
|
|
path.replaceWith(newErrorCall);
|
|
}
|
|
|
|
return {
|
|
visitor: {
|
|
NewExpression(path, file) {
|
|
if (path.get('callee').isIdentifier({name: 'Error'})) {
|
|
ErrorCallExpression(path, file);
|
|
}
|
|
},
|
|
|
|
CallExpression(path, file) {
|
|
if (path.get('callee').isIdentifier({name: 'Error'})) {
|
|
ErrorCallExpression(path, file);
|
|
return;
|
|
}
|
|
},
|
|
},
|
|
};
|
|
};
|