1
-/**
2
- * Copyright (c) Facebook, Inc. and its affiliates.
3
- *
4
- * This source code is licensed under the MIT license found in the
5
- * LICENSE file in the root directory of this source tree.
6
- */
7
-'use strict';
8
-
9
-const fs = require('fs');
10
-const evalToString = require('../shared/evalToString');
11
-const invertObject = require('./invertObject');
12
-
13
-module.exports = function(babel) {
14
- const t = babel.types;
15
-
16
- const SEEN_SYMBOL = Symbol('replace-invariant-error-codes.seen');
17
-
18
- // Generate a hygienic identifier
19
- function getProdInvariantIdentifier(path, file, localState) {
20
- if (!localState.prodInvariantIdentifier) {
21
- localState.prodInvariantIdentifier = file.addImport(
22
- 'shared/reactProdInvariant',
23
- 'default',
24
- 'prodInvariant'
25
- );
26
- }
27
- return localState.prodInvariantIdentifier;
28
- }
29
-
30
- const DEV_EXPRESSION = t.identifier('__DEV__');
31
-
32
- return {
33
- pre() {
34
- this.prodInvariantIdentifier = null;
35
- },
36
-
37
- visitor: {
38
- CallExpression: {
39
- exit(path, file) {
40
- const node = path.node;
41
- // Ignore if it's already been processed
42
- if (node[SEEN_SYMBOL]) {
43
- return;
44
- }
45
- // Insert `import PROD_INVARIANT from 'reactProdInvariant';`
46
- // before all `invariant()` calls.
47
- if (path.get('callee').isIdentifier({name: 'invariant'})) {
48
- // Turns this code:
49
- //
50
- // invariant(condition, argument, 'foo', 'bar');
51
- //
52
- // into this:
53
- //
54
- // if (!condition) {
55
- // if ("production" !== process.env.NODE_ENV) {
56
- // invariant(false, argument, 'foo', 'bar');
57
- // } else {
58
- // PROD_INVARIANT('XYZ', 'foo', 'bar');
59
- // }
60
- // }
61
- //
62
- // where
63
- // - `XYZ` is an error code: a unique identifier (a number string)
64
- // that references a verbose error message.
65
- // The mapping is stored in `scripts/error-codes/codes.json`.
66
- // - `PROD_INVARIANT` is the `reactProdInvariant` function that always throws with an error URL like
67
- // http://reactjs.org/docs/error-decoder.html?invariant=XYZ&args[]=foo&args[]=bar
68
- //
69
- // Specifically this does 3 things:
70
- // 1. Checks the condition first, preventing an extra function call.
71
- // 2. Adds an environment check so that verbose error messages aren't
72
- // shipped to production.
73
- // 3. Rewrites the call to `invariant` in production to `reactProdInvariant`
74
- // - `reactProdInvariant` is always renamed to avoid shadowing
75
- // The generated code is longer than the original code but will dead
76
- // code removal in a minifier will strip that out.
77
- const condition = node.arguments[0];
78
- const errorMsgLiteral = evalToString(node.arguments[1]);
79
-
80
- const devInvariant = t.callExpression(
81
- node.callee,
82
- [
83
- t.booleanLiteral(false),
84
- t.stringLiteral(errorMsgLiteral),
85
- ].concat(node.arguments.slice(2))
86
- );
87
-
88
- devInvariant[SEEN_SYMBOL] = true;
89
-
90
- // Avoid caching because we write it as we go.
91
- const existingErrorMap = JSON.parse(
92
- fs.readFileSync(__dirname + '/codes.json', 'utf-8')
93
- );
94
- const errorMap = invertObject(existingErrorMap);
95
-
96
- const localInvariantId = getProdInvariantIdentifier(
97
- path,
98
- file,
99
- this
100
- );
101
- const prodErrorId = errorMap[errorMsgLiteral];
102
- let body = null;
103
-
104
- if (prodErrorId === undefined) {
105
- // The error wasn't found in the map.
106
- // This is only expected to occur on master since we extract codes before releases.
107
- // Keep the original invariant.
108
- body = t.expressionStatement(devInvariant);
109
- } else {
110
- const prodInvariant = t.callExpression(
111
- localInvariantId,
112
- [t.stringLiteral(prodErrorId)].concat(node.arguments.slice(2))
113
- );
114
- prodInvariant[SEEN_SYMBOL] = true;
115
- // The error was found in the map.
116
- // Switch between development and production versions depending on the env.
117
- body = t.ifStatement(
118
- DEV_EXPRESSION,
119
- t.blockStatement([t.expressionStatement(devInvariant)]),
120
- t.blockStatement([t.expressionStatement(prodInvariant)])
121
- );
122
- }
123
-
124
- path.replaceWith(
125
- t.ifStatement(
126
- t.unaryExpression('!', condition),
127
- t.blockStatement([body])
128
- )
129
- );
130
- }
131
- },
132
- },
133
- },
134
- };
135
-};