Skip to content

Commit 6353756

Browse files
authored
Merge pull request #4586 from aryaemami59/fix-mangleErrors
2 parents 259892d + 9cc383e commit 6353756

File tree

2 files changed

+105
-35
lines changed

2 files changed

+105
-35
lines changed

packages/toolkit/scripts/mangleErrors.cjs renamed to packages/toolkit/scripts/mangleErrors.mts

Lines changed: 88 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,35 @@
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+
}
421

522
/**
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.
725
*
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.
1029
*/
11-
const evalToString = (ast) => {
30+
const evalToString = (
31+
ast: Node | { type: 'Literal'; value: string },
32+
): string => {
1233
switch (ast.type) {
1334
case 'StringLiteral':
1435
case 'Literal': // ESLint
@@ -33,29 +54,54 @@ const evalToString = (ast) => {
3354
}
3455

3556
/**
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.
3860
*
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.
4064
*
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.
4471
*
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>
4974
*
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+
* ```
5381
*
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+
* ```
57100
*/
58-
module.exports = (babel) => {
101+
export const mangleErrorsPlugin = (
102+
babel: Babel,
103+
options: MangleErrorsPluginOptions,
104+
): PluginObj<PluginPass & MangleErrorsPluginOptions> => {
59105
const t = babel.types
60106
// When the plugin starts up, we'll load in the existing file. This allows us to continually add to it so that the
61107
// indexes do not change between builds.
@@ -65,18 +111,25 @@ module.exports = (babel) => {
65111
if (fs.existsSync(errorsPath)) {
66112
errorsFiles = fs.readFileSync(errorsPath).toString()
67113
}
68-
let errors = Object.values(JSON.parse(errorsFiles || '{}'))
114+
const errors = Object.values(JSON.parse(errorsFiles || '{}'))
69115
// This variable allows us to skip writing back to the file if the errors array hasn't changed
70116
let changeInArray = false
71117

72118
return {
119+
name: 'mangle-errors-plugin',
73120
pre: () => {
74121
changeInArray = false
75122
},
76123
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+
}
78131
const args = path.node.argument.arguments
79-
const minify = file.opts.minify
132+
const { minify } = options
80133

81134
if (args && args[0]) {
82135
// Skip running this logic when certain types come up:
@@ -89,11 +142,14 @@ module.exports = (babel) => {
89142
path.node.argument.arguments[0].type === 'CallExpression' ||
90143
path.node.argument.arguments[0].type === 'ObjectExpression' ||
91144
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)
93147
) {
94148
return
95149
}
96150

151+
const errorName = path.node.argument.callee.name
152+
97153
const errorMsgLiteral = evalToString(path.node.argument.arguments[0])
98154

99155
if (errorMsgLiteral.includes('Super expression')) {
@@ -126,13 +182,13 @@ module.exports = (babel) => {
126182
if (minify) {
127183
path.replaceWith(
128184
t.throwStatement(
129-
t.newExpression(t.identifier('Error'), [prodMessage]),
185+
t.newExpression(t.identifier(errorName), [prodMessage]),
130186
),
131187
)
132188
} else {
133189
path.replaceWith(
134190
t.throwStatement(
135-
t.newExpression(t.identifier('Error'), [
191+
t.newExpression(t.identifier(errorName), [
136192
t.conditionalExpression(
137193
t.binaryExpression(
138194
'===',
@@ -157,3 +213,5 @@ module.exports = (babel) => {
157213
},
158214
}
159215
}
216+
217+
export default mangleErrorsPlugin

packages/toolkit/tsup.config.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import path from 'node:path'
66
import { fileURLToPath } from 'node:url'
77
import type { Options as TsupOptions } from 'tsup'
88
import { defineConfig } from 'tsup'
9+
import type { MangleErrorsPluginOptions } from './scripts/mangleErrors.mjs'
10+
import { mangleErrorsPlugin } from './scripts/mangleErrors.mjs'
911

1012
// No __dirname under Node ESM
1113
const __filename = fileURLToPath(import.meta.url)
@@ -129,18 +131,28 @@ if (process.env.NODE_ENV === 'production') {
129131

130132
// Extract error strings, replace them with error codes, and write messages to a file
131133
const mangleErrorsTransform: Plugin = {
132-
name: 'mangle-errors-plugin',
134+
name: mangleErrorsPlugin.name,
133135
setup(build) {
134-
const { onTransform } = getBuildExtensions(build, 'mangle-errors-plugin')
136+
const { onTransform } = getBuildExtensions(build, mangleErrorsPlugin.name)
135137

136138
onTransform({ loaders: ['ts', 'tsx'] }, async (args) => {
137139
try {
138-
const res = babel.transformSync(args.code, {
140+
const res = await babel.transformAsync(args.code, {
139141
parserOpts: {
140142
plugins: ['typescript', 'jsx'],
141143
},
142-
plugins: [['./scripts/mangleErrors.cjs', { minify: false }]],
143-
})!
144+
plugins: [
145+
[
146+
mangleErrorsPlugin,
147+
{ minify: false } satisfies MangleErrorsPluginOptions,
148+
],
149+
],
150+
})
151+
152+
if (res == null) {
153+
throw new Error('Babel transformAsync returned null')
154+
}
155+
144156
return {
145157
code: res.code!,
146158
map: res.map!,

0 commit comments

Comments
 (0)