@@ -245,87 +245,12 @@ export default function (): PluginObj {
245
245
246
246
visitedClasses . add ( classNode ) ;
247
247
248
- if ( hasPotentialSideEffects ) {
249
- return ;
250
- }
251
-
252
248
// If no statements to wrap, check for static class properties.
253
- // Static class properties may be downleveled at later stages in the build pipeline
254
- // which results in additional function calls outside the class body. These calls
255
- // then cause the class to be referenced and not eligible for removal. Since it is
256
- // not known at this stage whether the class needs to be downleveled, the transform
257
- // wraps classes preemptively to allow for potential removal within the optimization
258
- // stages.
259
- if ( wrapStatementPaths . length === 0 ) {
260
- let shouldWrap = false ;
261
- for ( const element of path . get ( 'body' ) . get ( 'body' ) ) {
262
- if ( element . isClassProperty ( ) ) {
263
- // Only need to analyze static properties
264
- if ( ! element . node . static ) {
265
- continue ;
266
- }
267
-
268
- // Check for potential side effects.
269
- // These checks are conservative and could potentially be expanded in the future.
270
- const elementKey = element . get ( 'key' ) ;
271
- const elementValue = element . get ( 'value' ) ;
272
- if (
273
- elementKey . isIdentifier ( ) &&
274
- ( ! elementValue . isExpression ( ) ||
275
- canWrapProperty ( elementKey . node . name , elementValue ) )
276
- ) {
277
- shouldWrap = true ;
278
- } else {
279
- // Not safe to wrap
280
- shouldWrap = false ;
281
- break ;
282
- }
283
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
284
- } else if ( ( element as any ) . isStaticBlock ( ) ) {
285
- // Only need to analyze static blocks
286
- const body = element . get ( 'body' ) ;
287
-
288
- if ( Array . isArray ( body ) && body . length > 1 ) {
289
- // Not safe to wrap
290
- shouldWrap = false ;
291
- break ;
292
- }
293
-
294
- const expression = body . find ( ( n : NodePath < types . Node > ) =>
295
- n . isExpressionStatement ( ) ,
296
- ) as NodePath < types . ExpressionStatement > | undefined ;
297
-
298
- const assignmentExpression = expression ?. get ( 'expression' ) ;
299
- if ( assignmentExpression ?. isAssignmentExpression ( ) ) {
300
- const left = assignmentExpression . get ( 'left' ) ;
301
- if ( ! left . isMemberExpression ( ) ) {
302
- continue ;
303
- }
304
-
305
- if ( ! left . get ( 'object' ) . isThisExpression ( ) ) {
306
- // Not safe to wrap
307
- shouldWrap = false ;
308
- break ;
309
- }
310
-
311
- const element = left . get ( 'property' ) ;
312
- const right = assignmentExpression . get ( 'right' ) ;
313
- if (
314
- element . isIdentifier ( ) &&
315
- ( ! right . isExpression ( ) || canWrapProperty ( element . node . name , right ) )
316
- ) {
317
- shouldWrap = true ;
318
- } else {
319
- // Not safe to wrap
320
- shouldWrap = false ;
321
- break ;
322
- }
323
- }
324
- }
325
- }
326
- if ( ! shouldWrap ) {
327
- return ;
328
- }
249
+ if (
250
+ hasPotentialSideEffects ||
251
+ ( wrapStatementPaths . length === 0 && ! analyzeClassStaticProperties ( path ) . shouldWrap )
252
+ ) {
253
+ return ;
329
254
}
330
255
331
256
const wrapStatementNodes : types . Statement [ ] = [ ] ;
@@ -359,9 +284,7 @@ export default function (): PluginObj {
359
284
const { node : classNode , parentPath } = path ;
360
285
const { wrapDecorators } = state . opts as { wrapDecorators : boolean } ;
361
286
362
- // Class expressions are used by TypeScript to represent downlevel class/constructor decorators.
363
- // If not wrapping decorators, they do not need to be processed.
364
- if ( ! wrapDecorators || visitedClasses . has ( classNode ) ) {
287
+ if ( visitedClasses . has ( classNode ) ) {
365
288
return ;
366
289
}
367
290
@@ -382,7 +305,11 @@ export default function (): PluginObj {
382
305
383
306
visitedClasses . add ( classNode ) ;
384
307
385
- if ( hasPotentialSideEffects || wrapStatementPaths . length === 0 ) {
308
+ // If no statements to wrap, check for static class properties.
309
+ if (
310
+ hasPotentialSideEffects ||
311
+ ( wrapStatementPaths . length === 0 && ! analyzeClassStaticProperties ( path ) . shouldWrap )
312
+ ) {
386
313
return ;
387
314
}
388
315
@@ -416,3 +343,82 @@ export default function (): PluginObj {
416
343
} ,
417
344
} ;
418
345
}
346
+
347
+ /**
348
+ * Static class properties may be downleveled at later stages in the build pipeline
349
+ * which results in additional function calls outside the class body. These calls
350
+ * then cause the class to be referenced and not eligible for removal. Since it is
351
+ * not known at this stage whether the class needs to be downleveled, the transform
352
+ * wraps classes preemptively to allow for potential removal within the optimization stages.
353
+ */
354
+ function analyzeClassStaticProperties (
355
+ path : NodePath < types . ClassDeclaration | types . ClassExpression > ,
356
+ ) : { shouldWrap : boolean } {
357
+ let shouldWrap = false ;
358
+ for ( const element of path . get ( 'body' ) . get ( 'body' ) ) {
359
+ if ( element . isClassProperty ( ) ) {
360
+ // Only need to analyze static properties
361
+ if ( ! element . node . static ) {
362
+ continue ;
363
+ }
364
+
365
+ // Check for potential side effects.
366
+ // These checks are conservative and could potentially be expanded in the future.
367
+ const elementKey = element . get ( 'key' ) ;
368
+ const elementValue = element . get ( 'value' ) ;
369
+ if (
370
+ elementKey . isIdentifier ( ) &&
371
+ ( ! elementValue . isExpression ( ) || canWrapProperty ( elementKey . node . name , elementValue ) )
372
+ ) {
373
+ shouldWrap = true ;
374
+ } else {
375
+ // Not safe to wrap
376
+ shouldWrap = false ;
377
+ break ;
378
+ }
379
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
380
+ } else if ( ( element as any ) . isStaticBlock ( ) ) {
381
+ // Only need to analyze static blocks
382
+ const body = element . get ( 'body' ) ;
383
+
384
+ if ( Array . isArray ( body ) && body . length > 1 ) {
385
+ // Not safe to wrap
386
+ shouldWrap = false ;
387
+ break ;
388
+ }
389
+
390
+ const expression = body . find ( ( n : NodePath < types . Node > ) => n . isExpressionStatement ( ) ) as
391
+ | NodePath < types . ExpressionStatement >
392
+ | undefined ;
393
+
394
+ const assignmentExpression = expression ?. get ( 'expression' ) ;
395
+ if ( assignmentExpression ?. isAssignmentExpression ( ) ) {
396
+ const left = assignmentExpression . get ( 'left' ) ;
397
+ if ( ! left . isMemberExpression ( ) ) {
398
+ continue ;
399
+ }
400
+
401
+ if ( ! left . get ( 'object' ) . isThisExpression ( ) ) {
402
+ // Not safe to wrap
403
+ shouldWrap = false ;
404
+ break ;
405
+ }
406
+
407
+ const element = left . get ( 'property' ) ;
408
+ const right = assignmentExpression . get ( 'right' ) ;
409
+ if (
410
+ element . isIdentifier ( ) &&
411
+ ( ! right . isExpression ( ) || canWrapProperty ( element . node . name , right ) )
412
+ ) {
413
+ shouldWrap = true ;
414
+ } else {
415
+ // Not safe to wrap
416
+ shouldWrap = false ;
417
+ break ;
418
+ }
419
+ }
420
+ }
421
+ }
422
+
423
+ return { shouldWrap } ;
424
+ }
0 commit comments