Skip to content

Commit 7505f8e

Browse files
authored
Merge pull request #3740 from reduxjs/feature/2.0-byte-shaving
2 parents 37c27ef + 3b9722d commit 7505f8e

26 files changed

+439
-137
lines changed

errors.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"0": "The object notation for `createReducer` has been removed. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createReducer",
3+
"1": "A case reducer on a non-draftable value must not return undefined",
4+
"2": ": ",
5+
"3": "prepareAction did not return an object",
6+
"4": "\"reducer\" is a required argument, and must be a function or an object of functions that can be passed to combineReducers",
7+
"5": "when using a middleware builder function, an array of middleware must be returned",
8+
"6": "each middleware provided to configureStore must be a function",
9+
"7": "\"enhancers\" field must be a callback",
10+
"8": "\"enhancers\" callback must return an array",
11+
"9": "each enhancer provided to configureStore must be a function",
12+
"10": "`name` is a required option for createSlice",
13+
"11": "The object notation for `createSlice.extraReducers` has been removed. Please use the 'builder callback' notation instead: https://redux-toolkit.js.org/api/createSlice",
14+
"12": "selectState returned undefined for an uninjected slice reducer",
15+
"13": "Please use the `create.preparedReducer` notation for prepared action creators with the `create` notation.",
16+
"14": "The slice reducer for key \"\" returned undefined when called for selector(). If the state passed to the reducer is undefined, you must explicitly return the initial state. The initial state may not be undefined. If you don't want to set a value for this reducer, you can use null instead of undefined.",
17+
"15": "original must be used on state Proxy",
18+
"16": "Creating or removing a listener requires one of the known fields for matching an action",
19+
"17": "Unsubscribe not initialized",
20+
"18": ": getOriginalState can only be called synchronously",
21+
"19": "`builder.addCase` should only be called before calling `builder.addMatcher`",
22+
"20": "`builder.addCase` should only be called before calling `builder.addDefaultCase`",
23+
"21": "addCase cannot be called with two reducers for the same action type",
24+
"22": "`builder.addMatcher` should only be called before calling `builder.addDefaultCase`",
25+
"23": "`builder.addDefaultCase` can only be called once",
26+
"24": " is not a function",
27+
"25": "When using `fakeBaseQuery`, all queries & mutations must use the `queryFn` definition syntax.",
28+
"26": "Warning: Middleware for RTK-Query API at reducerPath \"\" has not been added to the store.\nYou must add the middleware for RTK-Query to function correctly!",
29+
"27": "Warning: Middleware for RTK-Query API at reducerPath \"\" has not been added to the store.\n You must add the middleware for RTK-Query to function correctly!",
30+
"28": "Cannot refetch a query that has not been started yet."
31+
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"@babel/helper-compilation-targets": "7.19.3",
4545
"@babel/traverse": "7.19.3",
4646
"@babel/types": "7.19.3",
47-
"esbuild": "0.17.17",
47+
"esbuild": "0.19.3",
4848
"jest-snapshot": "29.3.1",
4949
"msw": "patch:msw@npm:0.40.2#.yarn/patches/msw-npm-0.40.2-2107d48752",
5050
"jscodeshift": "0.13.1",

packages/toolkit/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"axios": "^0.19.2",
6767
"console-testing-library": "0.6.1",
6868
"convert-source-map": "^1.7.0",
69+
"esbuild-extra": "^0.3.1",
6970
"eslint": "^7.25.0",
7071
"eslint-config-prettier": "^8.3.0",
7172
"eslint-config-react-app": "^7.0.1",
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
const fs = require('fs')
2+
const path = require('path')
3+
const helperModuleImports = require('@babel/helper-module-imports')
4+
5+
/**
6+
* Converts an AST type into a javascript string so that it can be added to the error message lookup.
7+
*
8+
* Adapted from React (https://github.com/facebook/react/blob/master/scripts/shared/evalToString.js) with some
9+
* adjustments
10+
*/
11+
const evalToString = (ast) => {
12+
switch (ast.type) {
13+
case 'StringLiteral':
14+
case 'Literal': // ESLint
15+
return ast.value
16+
case 'BinaryExpression': // `+`
17+
if (ast.operator !== '+') {
18+
throw new Error('Unsupported binary operator ' + ast.operator)
19+
}
20+
return evalToString(ast.left) + evalToString(ast.right)
21+
case 'TemplateLiteral':
22+
return ast.quasis.reduce(
23+
(concatenatedValue, templateElement) =>
24+
concatenatedValue + templateElement.value.raw,
25+
''
26+
)
27+
case 'Identifier':
28+
return ast.name
29+
default:
30+
console.log('Bad AST in mangleErrors -> evalToString(): ', ast)
31+
throw new Error(`Unsupported AST in evalToString: ${ast.type}, ${ast}`)
32+
}
33+
}
34+
35+
/**
36+
* Takes a `throw new error` statement and transforms it depending on the minify argument. Either option results in a
37+
* smaller bundle size in production for consumers.
38+
*
39+
* If minify is enabled, we'll replace the error message with just an index that maps to an arrow object lookup.
40+
*
41+
* If minify is disabled, we'll add in a conditional statement to check the process.env.NODE_ENV which will output a
42+
* an error number index in production or the actual error message in development. This allows consumers using webpack
43+
* or another build tool to have these messages in development but have just the error index in production.
44+
*
45+
* E.g.
46+
* Before:
47+
* throw new Error("This is my error message.");
48+
* throw new Error("This is a second error message.");
49+
*
50+
* After (with minify):
51+
* throw new Error(0);
52+
* throw new Error(1);
53+
*
54+
* After: (without minify):
55+
* throw new Error(node.process.NODE_ENV === 'production' ? 0 : "This is my error message.");
56+
* throw new Error(node.process.NODE_ENV === 'production' ? 1 : "This is a second error message.");
57+
*/
58+
module.exports = (babel) => {
59+
const t = babel.types
60+
// When the plugin starts up, we'll load in the existing file. This allows us to continually add to it so that the
61+
// indexes do not change between builds.
62+
let errorsFiles = ''
63+
// Save this to the root
64+
const errorsPath = path.join(__dirname, '../../../errors.json')
65+
if (fs.existsSync(errorsPath)) {
66+
errorsFiles = fs.readFileSync(errorsPath).toString()
67+
}
68+
let errors = Object.values(JSON.parse(errorsFiles || '{}'))
69+
// This variable allows us to skip writing back to the file if the errors array hasn't changed
70+
let changeInArray = false
71+
72+
return {
73+
pre: () => {
74+
changeInArray = false
75+
},
76+
visitor: {
77+
ThrowStatement(path, file) {
78+
const args = path.node.argument.arguments
79+
const minify = file.opts.minify
80+
81+
if (args && args[0]) {
82+
// Skip running this logic when certain types come up:
83+
// Identifier comes up when a variable is thrown (E.g. throw new error(message))
84+
// NumericLiteral, CallExpression, and ConditionalExpression is code we have already processed
85+
if (
86+
path.node.argument.arguments[0].type === 'Identifier' ||
87+
path.node.argument.arguments[0].type === 'NumericLiteral' ||
88+
path.node.argument.arguments[0].type === 'ConditionalExpression' ||
89+
path.node.argument.arguments[0].type === 'CallExpression' ||
90+
path.node.argument.arguments[0].type === 'ObjectExpression' ||
91+
path.node.argument.arguments[0].type === 'MemberExpression' ||
92+
path.node.argument.arguments[0]?.callee?.name === 'HandledError'
93+
) {
94+
return
95+
}
96+
97+
const errorMsgLiteral = evalToString(path.node.argument.arguments[0])
98+
99+
if (errorMsgLiteral.includes('Super expression')) {
100+
// ignore Babel runtime error message
101+
return
102+
}
103+
104+
// Attempt to get the existing index of the error. If it is not found, add it to the array as a new error.
105+
let errorIndex = errors.indexOf(errorMsgLiteral)
106+
if (errorIndex === -1) {
107+
errors.push(errorMsgLiteral)
108+
errorIndex = errors.length - 1
109+
changeInArray = true
110+
}
111+
112+
// Import the error message function
113+
const formatProdErrorMessageIdentifier = helperModuleImports.addNamed(
114+
path,
115+
'formatProdErrorMessage',
116+
'@reduxjs/toolkit',
117+
{ nameHint: 'formatProdErrorMessage' }
118+
)
119+
120+
// Creates a function call to output the message to the error code page on the website
121+
const prodMessage = t.callExpression(
122+
formatProdErrorMessageIdentifier,
123+
[t.numericLiteral(errorIndex)]
124+
)
125+
126+
if (minify) {
127+
path.replaceWith(
128+
t.throwStatement(
129+
t.newExpression(t.identifier('Error'), [prodMessage])
130+
)
131+
)
132+
} else {
133+
path.replaceWith(
134+
t.throwStatement(
135+
t.newExpression(t.identifier('Error'), [
136+
t.conditionalExpression(
137+
t.binaryExpression(
138+
'===',
139+
t.identifier('process.env.NODE_ENV'),
140+
t.stringLiteral('production')
141+
),
142+
prodMessage,
143+
path.node.argument.arguments[0]
144+
),
145+
])
146+
)
147+
)
148+
}
149+
}
150+
},
151+
},
152+
post: () => {
153+
// If there is a new error in the array, convert it to an indexed object and write it back to the file.
154+
if (changeInArray) {
155+
fs.writeFileSync(errorsPath, JSON.stringify({ ...errors }, null, 2))
156+
}
157+
},
158+
}
159+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* Adapted from React: https://github.com/facebook/react/blob/master/packages/shared/formatProdErrorMessage.js
3+
*
4+
* Do not require this module directly! Use normal throw error calls. These messages will be replaced with error codes
5+
* during build.
6+
* @param {number} code
7+
*/
8+
export function formatProdErrorMessage(code: number) {
9+
return (
10+
`Minified Redux Toolkit error #${code}; visit https://redux-toolkit.js.org/Errors?code=${code} for the full message or ` +
11+
'use the non-minified dev environment for full errors. '
12+
)
13+
}

packages/toolkit/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
// This must remain here so that the `mangleErrors.cjs` build script
2+
// does not have to import this into each source file it rewrites.
3+
import { formatProdErrorMessage } from './formatProdErrorMessage'
4+
15
export * from 'redux'
26
export {
37
produce as createNextState,
@@ -207,3 +211,5 @@ export { combineSlices } from './combineSlices'
207211
export type { WithSlice } from './combineSlices'
208212

209213
export type { ExtractDispatchExtensions as TSHelpersExtractDispatchExtensions } from './tsHelpers'
214+
215+
export { formatProdErrorMessage } from './formatProdErrorMessage'

packages/toolkit/src/listenerMiddleware/task.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ import { addAbortSignalListener, catchRejection, noop } from './utils'
1010
*/
1111
export const validateActive = (signal: AbortSignal): void => {
1212
if (signal.aborted) {
13-
throw new TaskAbortError((signal as AbortSignalWithReason<string>).reason)
13+
const { reason } = signal as AbortSignalWithReason<string>
14+
throw new TaskAbortError(reason)
1415
}
1516
}
1617

packages/toolkit/src/query/core/buildMiddleware/cacheLifecycle.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isAsyncThunkAction, isFulfilled } from '@reduxjs/toolkit'
1+
import { isAsyncThunkAction, isFulfilled } from '../rtkImports'
22
import type { UnknownAction } from 'redux'
33
import type { ThunkDispatch } from 'redux-thunk'
44
import type { BaseQueryFn, BaseQueryMeta } from '../../baseQueryTypes'

packages/toolkit/src/query/core/buildMiddleware/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {
44
ThunkDispatch,
55
UnknownAction,
66
} from '@reduxjs/toolkit'
7-
import { isAction, createAction } from '@reduxjs/toolkit'
7+
import { isAction, createAction } from '../rtkImports'
88

99
import type {
1010
EndpointDefinitions,

packages/toolkit/src/query/core/buildMiddleware/invalidationByTags.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isAnyOf, isFulfilled, isRejectedWithValue } from '@reduxjs/toolkit'
1+
import { isAnyOf, isFulfilled, isRejectedWithValue } from '../rtkImports'
22

33
import type { FullTagDescription } from '../../endpointDefinitions'
44
import { calculateProvidedBy } from '../../endpointDefinitions'

0 commit comments

Comments
 (0)