8
8
9
9
import { NodePath , PluginObj , PluginPass , types } from '@babel/core' ;
10
10
import annotateAsPure from '@babel/helper-annotate-as-pure' ;
11
+ import splitExportDeclaration from '@babel/helper-split-export-declaration' ;
11
12
12
13
/**
13
14
* The name of the Typescript decorator helper function created by the TypeScript compiler.
@@ -183,12 +184,18 @@ function analyzeClassSiblings(
183
184
}
184
185
185
186
/**
186
- * The set of classed already visited and analyzed during the plugin's execution.
187
+ * The set of classes already visited and analyzed during the plugin's execution.
187
188
* This is used to prevent adjusted classes from being repeatedly analyzed which can lead
188
189
* to an infinite loop.
189
190
*/
190
191
const visitedClasses = new WeakSet < types . Class > ( ) ;
191
192
193
+ /**
194
+ * A map of classes that have already been analyzed during the default export splitting step.
195
+ * This is used to avoid analyzing a class declaration twice if it is a direct default export.
196
+ */
197
+ const exportDefaultAnalysis = new WeakMap < types . Class , ReturnType < typeof analyzeClassSiblings > > ( ) ;
198
+
192
199
/**
193
200
* A babel plugin factory function for adjusting classes; primarily with Angular metadata.
194
201
* The adjustments include wrapping classes with known safe or no side effects with pure
@@ -201,6 +208,25 @@ const visitedClasses = new WeakSet<types.Class>();
201
208
export default function ( ) : PluginObj {
202
209
return {
203
210
visitor : {
211
+ // When a class is converted to a variable declaration, the default export must be moved
212
+ // to a subsequent statement to prevent a JavaScript syntax error.
213
+ ExportDefaultDeclaration ( path : NodePath < types . ExportDefaultDeclaration > , state : PluginPass ) {
214
+ const declaration = path . get ( 'declaration' ) ;
215
+ if ( ! declaration . isClassDeclaration ( ) ) {
216
+ return ;
217
+ }
218
+
219
+ const { wrapDecorators } = state . opts as { wrapDecorators : boolean } ;
220
+ const analysis = analyzeClassSiblings ( path , declaration . node . id , wrapDecorators ) ;
221
+ exportDefaultAnalysis . set ( declaration . node , analysis ) ;
222
+
223
+ // Splitting the export declaration is not needed if the class will not be wrapped
224
+ if ( analysis . hasPotentialSideEffects ) {
225
+ return ;
226
+ }
227
+
228
+ splitExportDeclaration ( path ) ;
229
+ } ,
204
230
ClassDeclaration ( path : NodePath < types . ClassDeclaration > , state : PluginPass ) {
205
231
const { node : classNode , parentPath } = path ;
206
232
const { wrapDecorators } = state . opts as { wrapDecorators : boolean } ;
@@ -210,14 +236,10 @@ export default function (): PluginObj {
210
236
}
211
237
212
238
// Analyze sibling statements for elements of the class that were downleveled
213
- const hasExport =
214
- parentPath . isExportNamedDeclaration ( ) || parentPath . isExportDefaultDeclaration ( ) ;
215
- const origin = hasExport ? parentPath : path ;
216
- const { wrapStatementPaths, hasPotentialSideEffects } = analyzeClassSiblings (
217
- origin ,
218
- classNode . id ,
219
- wrapDecorators ,
220
- ) ;
239
+ const origin = parentPath . isExportNamedDeclaration ( ) ? parentPath : path ;
240
+ const { wrapStatementPaths, hasPotentialSideEffects } =
241
+ exportDefaultAnalysis . get ( classNode ) ??
242
+ analyzeClassSiblings ( origin , classNode . id , wrapDecorators ) ;
221
243
222
244
visitedClasses . add ( classNode ) ;
223
245
@@ -288,18 +310,7 @@ export default function (): PluginObj {
288
310
const declaration = types . variableDeclaration ( 'let' , [
289
311
types . variableDeclarator ( types . cloneNode ( classNode . id ) , replacementInitializer ) ,
290
312
] ) ;
291
- if ( parentPath . isExportDefaultDeclaration ( ) ) {
292
- // When converted to a variable declaration, the default export must be moved
293
- // to a subsequent statement to prevent a JavaScript syntax error.
294
- parentPath . replaceWithMultiple ( [
295
- declaration ,
296
- types . exportNamedDeclaration ( undefined , [
297
- types . exportSpecifier ( types . cloneNode ( classNode . id ) , types . identifier ( 'default' ) ) ,
298
- ] ) ,
299
- ] ) ;
300
- } else {
301
- path . replaceWith ( declaration ) ;
302
- }
313
+ path . replaceWith ( declaration ) ;
303
314
} ,
304
315
ClassExpression ( path : NodePath < types . ClassExpression > , state : PluginPass ) {
305
316
const { node : classNode , parentPath } = path ;
0 commit comments