1
- const fs = require ( 'fs' )
2
- const path = require ( 'path' )
3
- const helperModuleImports = require ( '@babel/helper-module-imports' )
1
+ import type { Node , PluginObj , PluginPass } from '@babel/core'
2
+ import * as helperModuleImports from '@babel/helper-module-imports'
3
+ import * as fs from 'node:fs'
4
+ import * as path from 'node:path'
5
+
6
+ type Babel = typeof import ( '@babel/core' )
7
+
8
+ /**
9
+ * Represents the options for the {@linkcode mangleErrorsPlugin}.
10
+ *
11
+ * @internal
12
+ */
13
+ export interface MangleErrorsPluginOptions {
14
+ /**
15
+ * Whether to minify the error messages or not.
16
+ * If `true`, the error messages will be replaced with an index
17
+ * that maps object lookup.
18
+ */
19
+ minify : boolean
20
+ }
4
21
5
22
/**
6
- * Converts an AST type into a javascript string so that it can be added to the error message lookup.
23
+ * Converts an AST type into a JavaScript string so that it can be added to
24
+ * the error message lookup.
7
25
*
8
- * Adapted from React (https://github.com/facebook/react/blob/master/scripts/shared/evalToString.js) with some
9
- * adjustments
26
+ * Adapted from React
27
+ * {@linkcode https://github.com/facebook/react/blob/master/scripts/shared/evalToString.js | evalToString}
28
+ * with some adjustments.
10
29
*/
11
- const evalToString = ( ast ) => {
30
+ const evalToString = (
31
+ ast : Node | { type : 'Literal' ; value : string } ,
32
+ ) : string => {
12
33
switch ( ast . type ) {
13
34
case 'StringLiteral' :
14
35
case 'Literal' : // ESLint
@@ -33,29 +54,54 @@ const evalToString = (ast) => {
33
54
}
34
55
35
56
/**
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.
57
+ * Transforms a `throw new Error` statement based on the
58
+ * {@linkcode MangleErrorsPluginOptions.minify | minify} argument,
59
+ * resulting in a smaller bundle size for consumers in production.
38
60
*
39
- * If minify is enabled, we'll replace the error message with just an index that maps to an arrow object lookup.
61
+ * If {@linkcode MangleErrorsPluginOptions.minify | minify} is enabled,
62
+ * the error message will be replaced with an index that maps to
63
+ * an object lookup.
40
64
*
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.
65
+ * If {@linkcode MangleErrorsPluginOptions.minify | minify} is disabled,
66
+ * a conditional statement will be added to check `process.env.NODE_ENV`,
67
+ * which will output an error number index in production or the actual
68
+ * error message in development. This allows consumers using Webpack or
69
+ * another build tool to have these messages in development but only the
70
+ * error index in production.
44
71
*
45
- * E.g.
46
- * Before:
47
- * throw new Error("This is my error message.");
48
- * throw new Error("This is a second error message.");
72
+ * @example
73
+ * <caption>__Before:__</caption>
49
74
*
50
- * After (with minify):
51
- * throw new Error(0);
52
- * throw new Error(1);
75
+ * ```ts
76
+ * throw new Error('each middleware provided to configureStore must be a function');
77
+ * throw new Error(
78
+ * '`reducer` is a required argument, and must be a function or an object of functions that can be passed to combineReducers',
79
+ * )
80
+ * ```
53
81
*
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.");
82
+ * @example
83
+ * <caption>__After (with minify):__</caption>
84
+ *
85
+ * ```ts
86
+ * throw new Error(formatProdErrorMessage(0));
87
+ * throw new Error(formatProdErrorMessage(1));
88
+ * ```
89
+ *
90
+ * @example
91
+ * <caption>__After (without minify):__</caption>
92
+ *
93
+ * ```ts
94
+ * throw new Error(
95
+ * process.env.NODE_ENV === 'production'
96
+ * ? formatProdErrorMessage(4)
97
+ * : 'each middleware provided to configureStore must be a function',
98
+ * )
99
+ * ```
57
100
*/
58
- module . exports = ( babel ) => {
101
+ export const mangleErrorsPlugin = (
102
+ babel : Babel ,
103
+ options : MangleErrorsPluginOptions ,
104
+ ) : PluginObj < PluginPass & MangleErrorsPluginOptions > => {
59
105
const t = babel . types
60
106
// When the plugin starts up, we'll load in the existing file. This allows us to continually add to it so that the
61
107
// indexes do not change between builds.
@@ -65,18 +111,25 @@ module.exports = (babel) => {
65
111
if ( fs . existsSync ( errorsPath ) ) {
66
112
errorsFiles = fs . readFileSync ( errorsPath ) . toString ( )
67
113
}
68
- let errors = Object . values ( JSON . parse ( errorsFiles || '{}' ) )
114
+ const errors = Object . values ( JSON . parse ( errorsFiles || '{}' ) )
69
115
// This variable allows us to skip writing back to the file if the errors array hasn't changed
70
116
let changeInArray = false
71
117
72
118
return {
119
+ name : 'mangle-errors-plugin' ,
73
120
pre : ( ) => {
74
121
changeInArray = false
75
122
} ,
76
123
visitor : {
77
- ThrowStatement ( path , file ) {
124
+ ThrowStatement ( path ) {
125
+ if (
126
+ ! ( 'arguments' in path . node . argument ) ||
127
+ ! t . isNewExpression ( path . node . argument )
128
+ ) {
129
+ return
130
+ }
78
131
const args = path . node . argument . arguments
79
- const minify = file . opts . minify
132
+ const { minify } = options
80
133
81
134
if ( args && args [ 0 ] ) {
82
135
// Skip running this logic when certain types come up:
@@ -89,11 +142,14 @@ module.exports = (babel) => {
89
142
path . node . argument . arguments [ 0 ] . type === 'CallExpression' ||
90
143
path . node . argument . arguments [ 0 ] . type === 'ObjectExpression' ||
91
144
path . node . argument . arguments [ 0 ] . type === 'MemberExpression' ||
92
- path . node . argument . arguments [ 0 ] ?. callee ?. name === 'HandledError'
145
+ ! t . isExpression ( path . node . argument . arguments [ 0 ] ) ||
146
+ ! t . isIdentifier ( path . node . argument . callee )
93
147
) {
94
148
return
95
149
}
96
150
151
+ const errorName = path . node . argument . callee . name
152
+
97
153
const errorMsgLiteral = evalToString ( path . node . argument . arguments [ 0 ] )
98
154
99
155
if ( errorMsgLiteral . includes ( 'Super expression' ) ) {
@@ -126,13 +182,13 @@ module.exports = (babel) => {
126
182
if ( minify ) {
127
183
path . replaceWith (
128
184
t . throwStatement (
129
- t . newExpression ( t . identifier ( 'Error' ) , [ prodMessage ] ) ,
185
+ t . newExpression ( t . identifier ( errorName ) , [ prodMessage ] ) ,
130
186
) ,
131
187
)
132
188
} else {
133
189
path . replaceWith (
134
190
t . throwStatement (
135
- t . newExpression ( t . identifier ( 'Error' ) , [
191
+ t . newExpression ( t . identifier ( errorName ) , [
136
192
t . conditionalExpression (
137
193
t . binaryExpression (
138
194
'===' ,
@@ -157,3 +213,5 @@ module.exports = (babel) => {
157
213
} ,
158
214
}
159
215
}
216
+
217
+ export default mangleErrorsPlugin
0 commit comments