FB bundles wrap warning() calls in __DEV__ (#10314)
FB bundles wrap warning() calls in __DEV__ Split dev-mode transforms into separate parts: 1) umd+cjs+fb: Wrap warning calls with process.env checks 2) umd+cjs: Replace error messages with minified codes Also updated transforms to use __DEV__ since it transforms to smaller code after stripEnvVariables is run. Also renamed 'scripts/error-codes/dev-expression-with-codes.js' -> 'scripts/error-codes/replace-invariant-error-codes.js'
This commit is contained in:
parent
8890db707b
commit
f3e502c613
|
@ -9,7 +9,7 @@ Prior to this release, we stripped out error messages at build-time and this is
|
|||
|
||||
> Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.
|
||||
|
||||
In order to make debugging in production easier, we're introducing an Error Code System in [15.2.0](https://github.com/facebook/react/releases/tag/v15.2.0). We developed a [gulp script](https://github.com/facebook/react/blob/master/scripts/error-codes/gulp-extract-errors.js) that collects all of our `invariant` error messages and folds them to a [JSON file](https://github.com/facebook/react/blob/master/scripts/error-codes/codes.json), and at build-time Babel uses the JSON to [rewrite](https://github.com/facebook/react/blob/master/scripts/error-codes/dev-expression-with-codes.js) our `invariant` calls in production to reference the corresponding error IDs. Now when things go wrong in production, the error that React throws will contain a URL with an error ID and relevant information. The URL will point you to a page in our documentation where the original error message gets reassembled.
|
||||
In order to make debugging in production easier, we're introducing an Error Code System in [15.2.0](https://github.com/facebook/react/releases/tag/v15.2.0). We developed a [gulp script](https://github.com/facebook/react/blob/master/scripts/error-codes/gulp-extract-errors.js) that collects all of our `invariant` error messages and folds them to a [JSON file](https://github.com/facebook/react/blob/master/scripts/error-codes/codes.json), and at build-time Babel uses the JSON to [rewrite](https://github.com/facebook/react/blob/master/scripts/error-codes/replace-invariant-error-codes.js) our `invariant` calls in production to reference the corresponding error IDs. Now when things go wrong in production, the error that React throws will contain a URL with an error ID and relevant information. The URL will point you to a page in our documentation where the original error message gets reassembled.
|
||||
|
||||
While we hope you don't see errors often, you can see how it works [here](/react/docs/error-decoder.html?invariant=109&args[]=Foo). This is what the same error from above will look like:
|
||||
|
||||
|
|
|
@ -9,6 +9,6 @@ The error code system substitutes React's invariant error messages with error ID
|
|||
The error code system consists of 5 parts:
|
||||
- [`codes.json`](https://github.com/facebook/react/blob/master/scripts/error-codes/codes.json) contains the mapping from IDs to error messages. This file is generated by the Gulp plugin and is used by both the Babel plugin and the error decoder page in our documentation. This file is append-only, which means an existing code in the file will never be changed/removed.
|
||||
- [`extract-errors.js`](https://github.com/facebook/react/blob/master/scripts/error-codes/extract-errors.js) is an node script that traverses our codebase and updates `codes.json`. Use it by calling `yarn build -- --extract-errors`.
|
||||
- [`dev-expression-with-codes.js`](https://github.com/facebook/react/blob/master/scripts/error-codes/dev-expression-with-codes.js) is a Babel pass that rewrites error messages to IDs for a production (minified) build.
|
||||
- [`replace-invariant-error-codes.js`](https://github.com/facebook/react/blob/master/scripts/error-codes/replace-invariant-error-codes.js) is a Babel pass that rewrites error messages to IDs for a production (minified) build.
|
||||
- [`reactProdInvariant.js`](https://github.com/facebook/react/blob/master/src/shared/utils/reactProdInvariant.js) is the replacement for `invariant` in production. This file gets imported by the Babel plugin and should _not_ be used manually.
|
||||
- [`ErrorDecoderComponent`](https://github.com/facebook/react/blob/master/docs/_js/ErrorDecoderComponent.js) is a React component that lives at https://facebook.github.io/react/docs/error-decoder.html. This page takes parameters like `?invariant=109&args[]=Foo` and displays a corresponding error message. Our documentation site's [`Rakefile`](https://github.com/facebook/react/blob/master/docs/Rakefile#L64-L69) has a task (`bundle exec rake copy_error_codes`) for adding the latest `codes.json` to the error decoder page. This task is included in the default `bundle exec rake release` task.
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
'use strict';
|
||||
|
||||
let babel = require('babel-core');
|
||||
let devExpressionWithCodes = require('../dev-expression-with-codes');
|
||||
let devExpressionWithCodes = require('../replace-invariant-error-codes');
|
||||
|
||||
function transform(input) {
|
||||
return babel.transform(input, {
|
||||
|
@ -35,26 +35,6 @@ describe('dev-expression', () => {
|
|||
process.env.NODE_ENV = oldEnv;
|
||||
});
|
||||
|
||||
it('should replace __DEV__ in if', () => {
|
||||
compare(
|
||||
`
|
||||
if (__DEV__) {
|
||||
console.log('foo')
|
||||
}`,
|
||||
`
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.log('foo');
|
||||
}`
|
||||
);
|
||||
});
|
||||
|
||||
it('should replace warning calls', () => {
|
||||
compare(
|
||||
"warning(condition, 'a %s b', 'c');",
|
||||
"process.env.NODE_ENV !== 'production' ? warning(condition, 'a %s b', 'c') : void 0;"
|
||||
);
|
||||
});
|
||||
|
||||
it("should add `reactProdInvariant` when it finds `require('invariant')`", () => {
|
||||
compare(
|
||||
"var invariant = require('invariant');",
|
||||
|
@ -69,7 +49,7 @@ var invariant = require('invariant');`
|
|||
"invariant(condition, 'Do not override existing functions.');",
|
||||
"var _prodInvariant = require('reactProdInvariant');\n\n" +
|
||||
'!condition ? ' +
|
||||
"process.env.NODE_ENV !== 'production' ? " +
|
||||
'__DEV__ ? ' +
|
||||
"invariant(false, 'Do not override existing functions.') : " +
|
||||
`_prodInvariant('16') : void 0;`
|
||||
);
|
||||
|
@ -78,7 +58,7 @@ var invariant = require('invariant');`
|
|||
it('should only add `reactProdInvariant` once', () => {
|
||||
var expectedInvariantTransformResult =
|
||||
'!condition ? ' +
|
||||
"process.env.NODE_ENV !== 'production' ? " +
|
||||
'__DEV__ ? ' +
|
||||
"invariant(false, 'Do not override existing functions.') : " +
|
||||
`_prodInvariant('16') : void 0;`;
|
||||
|
||||
|
@ -99,7 +79,7 @@ ${expectedInvariantTransformResult}`
|
|||
"invariant(condition, 'Expected %s target to be an array; got %s', 'foo', 'bar');",
|
||||
"var _prodInvariant = require('reactProdInvariant');\n\n" +
|
||||
'!condition ? ' +
|
||||
"process.env.NODE_ENV !== 'production' ? " +
|
||||
'__DEV__ ? ' +
|
||||
"invariant(false, 'Expected %s target to be an array; got %s', 'foo', 'bar') : " +
|
||||
`_prodInvariant('7', 'foo', 'bar') : void 0;`
|
||||
);
|
||||
|
@ -110,7 +90,7 @@ ${expectedInvariantTransformResult}`
|
|||
"invariant(condition, 'Expected a component class, ' + 'got %s.' + '%s', 'Foo', 'Bar');",
|
||||
"var _prodInvariant = require('reactProdInvariant');\n\n" +
|
||||
'!condition ? ' +
|
||||
"process.env.NODE_ENV !== 'production' ? " +
|
||||
'__DEV__ ? ' +
|
||||
"invariant(false, 'Expected a component class, got %s.%s', 'Foo', 'Bar') : " +
|
||||
`_prodInvariant('18', 'Foo', 'Bar') : void 0;`
|
||||
);
|
||||
|
|
|
@ -17,7 +17,7 @@ var errorMap = invertObject(existingErrorMap);
|
|||
module.exports = function(babel) {
|
||||
var t = babel.types;
|
||||
|
||||
var SEEN_SYMBOL = Symbol('dev-expression-with-codes.seen');
|
||||
var SEEN_SYMBOL = Symbol('replace-invariant-error-codes.seen');
|
||||
|
||||
// Generate a hygienic identifier
|
||||
function getProdInvariantIdentifier(path, localState) {
|
||||
|
@ -35,15 +35,7 @@ module.exports = function(babel) {
|
|||
return localState.prodInvariantIdentifier;
|
||||
}
|
||||
|
||||
var DEV_EXPRESSION = t.binaryExpression(
|
||||
'!==',
|
||||
t.memberExpression(
|
||||
t.memberExpression(t.identifier('process'), t.identifier('env'), false),
|
||||
t.identifier('NODE_ENV'),
|
||||
false
|
||||
),
|
||||
t.stringLiteral('production')
|
||||
);
|
||||
var DEV_EXPRESSION = t.identifier('__DEV__');
|
||||
|
||||
return {
|
||||
pre: function() {
|
||||
|
@ -51,18 +43,6 @@ module.exports = function(babel) {
|
|||
},
|
||||
|
||||
visitor: {
|
||||
Identifier: {
|
||||
enter: function(path) {
|
||||
// Do nothing when testing
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return;
|
||||
}
|
||||
// Replace __DEV__ with process.env.NODE_ENV !== 'production'
|
||||
if (path.isIdentifier({name: '__DEV__'})) {
|
||||
path.replaceWith(DEV_EXPRESSION);
|
||||
}
|
||||
},
|
||||
},
|
||||
CallExpression: {
|
||||
exit: function(path) {
|
||||
var node = path.node;
|
||||
|
@ -149,28 +129,6 @@ module.exports = function(babel) {
|
|||
])
|
||||
)
|
||||
);
|
||||
} else if (path.get('callee').isIdentifier({name: 'warning'})) {
|
||||
// Turns this code:
|
||||
//
|
||||
// warning(condition, argument, argument);
|
||||
//
|
||||
// into this:
|
||||
//
|
||||
// if ("production" !== process.env.NODE_ENV) {
|
||||
// warning(condition, argument, argument);
|
||||
// }
|
||||
//
|
||||
// The goal is to strip out warning calls entirely in production. We
|
||||
// don't need the same optimizations for conditions that we use for
|
||||
// invariant because we don't care about an extra call in __DEV__
|
||||
|
||||
node[SEEN_SYMBOL] = true;
|
||||
path.replaceWith(
|
||||
t.ifStatement(
|
||||
DEV_EXPRESSION,
|
||||
t.blockStatement([t.expressionStatement(node)])
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
|
@ -20,7 +20,7 @@ eslint-rules/__tests__/warning-and-invariant-args-test.js
|
|||
* warning(true, 'error!');
|
||||
* warning(true, '%s %s, %s %s: %s (%s)', 1, 2, 3, 4, 5, 6);
|
||||
|
||||
scripts/error-codes/__tests__/dev-expression-with-codes-test.js
|
||||
scripts/error-codes/__tests__/replace-invariant-error-codes-test.js
|
||||
* should replace __DEV__ in if
|
||||
* should replace warning calls
|
||||
* should add `reactProdInvariant` when it finds `require('invariant')`
|
||||
|
|
|
@ -25,7 +25,7 @@ var pathToBabel = path.join(
|
|||
);
|
||||
var pathToModuleMap = require.resolve('fbjs/module-map');
|
||||
var pathToBabelPluginDevWithCode = require.resolve(
|
||||
'../error-codes/dev-expression-with-codes'
|
||||
'../error-codes/replace-invariant-error-codes'
|
||||
);
|
||||
var pathToBabelPluginModules = require.resolve(
|
||||
'fbjs-scripts/babel-6/rewrite-modules'
|
||||
|
|
|
@ -118,6 +118,14 @@ function getFooter(bundleType) {
|
|||
|
||||
function updateBabelConfig(babelOpts, bundleType) {
|
||||
switch (bundleType) {
|
||||
case FB_DEV:
|
||||
case FB_PROD:
|
||||
return Object.assign({}, babelOpts, {
|
||||
plugins: babelOpts.plugins.concat([
|
||||
// Wrap warning() calls in a __DEV__ check so they are stripped from production.
|
||||
require('./plugins/wrap-warning-with-env-check'),
|
||||
]),
|
||||
});
|
||||
case UMD_DEV:
|
||||
case UMD_PROD:
|
||||
case NODE_DEV:
|
||||
|
@ -126,8 +134,12 @@ function updateBabelConfig(babelOpts, bundleType) {
|
|||
plugins: babelOpts.plugins.concat([
|
||||
// Use object-assign polyfill in open source
|
||||
resolve('./scripts/babel/transform-object-assign-require'),
|
||||
// Replace __DEV__ with process.env.NODE_ENV and minify invariant messages
|
||||
require('../error-codes/dev-expression-with-codes'),
|
||||
|
||||
// Minify invariant messages
|
||||
require('../error-codes/replace-invariant-error-codes'),
|
||||
|
||||
// Wrap warning() calls in a __DEV__ check so they are stripped from production.
|
||||
require('./plugins/wrap-warning-with-env-check'),
|
||||
]),
|
||||
});
|
||||
default:
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
/* eslint-disable quotes */
|
||||
'use strict';
|
||||
|
||||
let babel = require('babel-core');
|
||||
let wrapWarningWithEnvCheck = require('../wrap-warning-with-env-check');
|
||||
|
||||
function transform(input) {
|
||||
return babel.transform(input, {
|
||||
plugins: [wrapWarningWithEnvCheck],
|
||||
}).code;
|
||||
}
|
||||
|
||||
function compare(input, output) {
|
||||
var compiled = transform(input);
|
||||
expect(compiled).toEqual(output);
|
||||
}
|
||||
|
||||
var oldEnv;
|
||||
|
||||
describe('wrap-warning-with-env-check', () => {
|
||||
beforeEach(() => {
|
||||
oldEnv = process.env.NODE_ENV;
|
||||
process.env.NODE_ENV = '';
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env.NODE_ENV = oldEnv;
|
||||
});
|
||||
|
||||
it('should wrap warning calls', () => {
|
||||
compare(
|
||||
"warning(condition, 'a %s b', 'c');",
|
||||
"__DEV__ ? warning(condition, 'a %s b', 'c') : void 0;"
|
||||
);
|
||||
});
|
||||
|
||||
it('should not wrap invariant calls', () => {
|
||||
compare(
|
||||
"invariant(condition, 'a %s b', 'c');",
|
||||
"invariant(condition, 'a %s b', 'c');"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
module.exports = function(babel, options) {
|
||||
var t = babel.types;
|
||||
|
||||
var DEV_EXPRESSION = t.identifier('__DEV__');
|
||||
|
||||
var SEEN_SYMBOL = Symbol('expression.seen');
|
||||
|
||||
return {
|
||||
visitor: {
|
||||
CallExpression: {
|
||||
exit: function(path) {
|
||||
var node = path.node;
|
||||
|
||||
// Ignore if it's already been processed
|
||||
if (node[SEEN_SYMBOL]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (path.get('callee').isIdentifier({name: 'warning'})) {
|
||||
node[SEEN_SYMBOL] = true;
|
||||
|
||||
// Turns this code:
|
||||
//
|
||||
// warning(condition, argument, argument);
|
||||
//
|
||||
// into this:
|
||||
//
|
||||
// if (__DEV__) {
|
||||
// warning(condition, argument, argument);
|
||||
// }
|
||||
//
|
||||
// The goal is to strip out warning calls entirely in production.
|
||||
path.replaceWith(
|
||||
t.ifStatement(
|
||||
DEV_EXPRESSION,
|
||||
t.blockStatement([t.expressionStatement(node)])
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
Loading…
Reference in New Issue