88
99import { NodePath , PluginObj , PluginPass , types } from '@babel/core' ;
1010import annotateAsPure from '@babel/helper-annotate-as-pure' ;
11+ import splitExportDeclaration from '@babel/helper-split-export-declaration' ;
1112
1213/**
1314 * The name of the Typescript decorator helper function created by the TypeScript compiler.
@@ -183,12 +184,18 @@ function analyzeClassSiblings(
183184}
184185
185186/**
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.
187188 * This is used to prevent adjusted classes from being repeatedly analyzed which can lead
188189 * to an infinite loop.
189190 */
190191const visitedClasses = new WeakSet < types . Class > ( ) ;
191192
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+
192199/**
193200 * A babel plugin factory function for adjusting classes; primarily with Angular metadata.
194201 * The adjustments include wrapping classes with known safe or no side effects with pure
@@ -201,6 +208,25 @@ const visitedClasses = new WeakSet<types.Class>();
201208export default function ( ) : PluginObj {
202209 return {
203210 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+ } ,
204230 ClassDeclaration ( path : NodePath < types . ClassDeclaration > , state : PluginPass ) {
205231 const { node : classNode , parentPath } = path ;
206232 const { wrapDecorators } = state . opts as { wrapDecorators : boolean } ;
@@ -210,14 +236,10 @@ export default function (): PluginObj {
210236 }
211237
212238 // 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 ) ;
221243
222244 visitedClasses . add ( classNode ) ;
223245
@@ -288,18 +310,7 @@ export default function (): PluginObj {
288310 const declaration = types . variableDeclaration ( 'let' , [
289311 types . variableDeclarator ( types . cloneNode ( classNode . id ) , replacementInitializer ) ,
290312 ] ) ;
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 ) ;
303314 } ,
304315 ClassExpression ( path : NodePath < types . ClassExpression > , state : PluginPass ) {
305316 const { node : classNode , parentPath } = path ;
0 commit comments