@@ -13,9 +13,10 @@ import {
1313 R3HmrNamespaceDependency ,
1414 outputAst as o ,
1515} from '@angular/compiler' ;
16- import { DeclarationNode } from '../../reflection' ;
16+ import { DeclarationNode , ReflectionHost } from '../../reflection' ;
1717import { CompileResult } from '../../transform' ;
1818import ts from 'typescript' ;
19+ import { EnumValue , PartialEvaluator } from '../../partial_evaluator' ;
1920
2021/**
2122 * Determines the file-level dependencies that the HMR initializer needs to capture and pass along.
@@ -33,7 +34,12 @@ export function extractHmrDependencies(
3334 deferBlockMetadata : R3ComponentDeferMetadata ,
3435 classMetadata : o . Statement | null ,
3536 debugInfo : o . Statement | null ,
36- ) : { local : string [ ] ; external : R3HmrNamespaceDependency [ ] } {
37+ reflection : ReflectionHost ,
38+ evaluator : PartialEvaluator ,
39+ ) : {
40+ local : { name : string ; runtimeRepresentation : o . Expression } [ ] ;
41+ external : R3HmrNamespaceDependency [ ] ;
42+ } {
3743 const name = ts . isClassDeclaration ( node ) && node . name ? node . name . text : null ;
3844 const visitor = new PotentialTopLevelReadsVisitor ( ) ;
3945 const sourceFile = node . getSourceFile ( ) ;
@@ -57,16 +63,73 @@ export function extractHmrDependencies(
5763 // variables inside of functions. Note that we filter out the class name since it is always
5864 // defined and it saves us having to repeat this logic wherever the locals are consumed.
5965 const availableTopLevel = getTopLevelDeclarationNames ( sourceFile ) ;
66+ const local : { name : string ; runtimeRepresentation : o . Expression } [ ] = [ ] ;
67+ const seenLocals = new Set < string > ( ) ;
68+
69+ for ( const readNode of visitor . allReads ) {
70+ const readName = readNode instanceof o . ReadVarExpr ? readNode . name : readNode . text ;
71+
72+ if ( readName !== name && ! seenLocals . has ( readName ) && availableTopLevel . has ( readName ) ) {
73+ local . push ( {
74+ name : readName ,
75+ runtimeRepresentation : getRuntimeRepresentation ( readNode , reflection , evaluator ) ,
76+ } ) ;
77+ seenLocals . add ( readName ) ;
78+ }
79+ }
6080
6181 return {
62- local : Array . from ( visitor . allReads ) . filter ( ( r ) => r !== name && availableTopLevel . has ( r ) ) ,
82+ local,
6383 external : Array . from ( visitor . namespaceReads , ( name , index ) => ( {
6484 moduleName : name ,
6585 assignedName : `ɵhmr${ index } ` ,
6686 } ) ) ,
6787 } ;
6888}
6989
90+ /**
91+ * Gets a node that can be used to represent an identifier in the HMR replacement code at runtime.
92+ */
93+ function getRuntimeRepresentation (
94+ node : o . ReadVarExpr | ts . Identifier ,
95+ reflection : ReflectionHost ,
96+ evaluator : PartialEvaluator ,
97+ ) : o . Expression {
98+ if ( node instanceof o . ReadVarExpr ) {
99+ return o . variable ( node . name ) ;
100+ }
101+
102+ // Const enums can't be passed by reference, because their values are inlined.
103+ // Pass in an object literal with all of the values instead.
104+ if ( isConstEnumReference ( node , reflection ) ) {
105+ const evaluated = evaluator . evaluate ( node ) ;
106+
107+ if ( evaluated instanceof Map ) {
108+ const members : { key : string ; quoted : boolean ; value : o . Expression } [ ] = [ ] ;
109+
110+ for ( const [ name , value ] of evaluated . entries ( ) ) {
111+ if (
112+ value instanceof EnumValue &&
113+ ( value . resolved == null ||
114+ typeof value . resolved === 'string' ||
115+ typeof value . resolved === 'boolean' ||
116+ typeof value . resolved === 'number' )
117+ ) {
118+ members . push ( {
119+ key : name ,
120+ quoted : false ,
121+ value : o . literal ( value . resolved ) ,
122+ } ) ;
123+ }
124+ }
125+
126+ return o . literalMap ( members ) ;
127+ }
128+ }
129+
130+ return o . variable ( node . text ) ;
131+ }
132+
70133/**
71134 * Gets the names of all top-level declarations within the file (imports, declared classes etc).
72135 * @param sourceFile File in which to search for locals.
@@ -81,8 +144,7 @@ function getTopLevelDeclarationNames(sourceFile: ts.SourceFile): Set<string> {
81144 if (
82145 ts . isClassDeclaration ( node ) ||
83146 ts . isFunctionDeclaration ( node ) ||
84- ( ts . isEnumDeclaration ( node ) &&
85- ! node . modifiers ?. some ( ( m ) => m . kind === ts . SyntaxKind . ConstKeyword ) )
147+ ts . isEnumDeclaration ( node )
86148 ) {
87149 if ( node . name ) {
88150 results . add ( node . name . text ) ;
@@ -157,7 +219,7 @@ function trackBindingName(node: ts.BindingName, results: Set<string>): void {
157219 * inside functions.
158220 */
159221class PotentialTopLevelReadsVisitor extends o . RecursiveAstVisitor {
160- readonly allReads = new Set < string > ( ) ;
222+ readonly allReads = new Set < o . ReadVarExpr | ts . Identifier > ( ) ;
161223 readonly namespaceReads = new Set < string > ( ) ;
162224
163225 override visitExternalExpr ( ast : o . ExternalExpr , context : any ) {
@@ -168,7 +230,7 @@ class PotentialTopLevelReadsVisitor extends o.RecursiveAstVisitor {
168230 }
169231
170232 override visitReadVarExpr ( ast : o . ReadVarExpr , context : any ) {
171- this . allReads . add ( ast . name ) ;
233+ this . allReads . add ( ast ) ;
172234 super . visitReadVarExpr ( ast , context ) ;
173235 }
174236
@@ -186,7 +248,7 @@ class PotentialTopLevelReadsVisitor extends o.RecursiveAstVisitor {
186248 */
187249 private addAllTopLevelIdentifiers = ( node : ts . Node ) => {
188250 if ( ts . isIdentifier ( node ) && this . isTopLevelIdentifierReference ( node ) ) {
189- this . allReads . add ( node . text ) ;
251+ this . allReads . add ( node ) ;
190252 } else {
191253 ts . forEachChild ( node , this . addAllTopLevelIdentifiers ) ;
192254 }
@@ -326,3 +388,25 @@ class PotentialTopLevelReadsVisitor extends o.RecursiveAstVisitor {
326388 return ! ! value && typeof value . kind === 'number' ;
327389 }
328390}
391+
392+ /** Checks whether a node is a reference to a const enum. */
393+ function isConstEnumReference ( node : ts . Identifier , reflection : ReflectionHost ) : boolean {
394+ const parent = node . parent ;
395+
396+ // Only check identifiers that are in the form of `Foo.bar` where `Foo` is the node being checked.
397+ if (
398+ ! parent ||
399+ ! ts . isPropertyAccessExpression ( parent ) ||
400+ parent . expression !== node ||
401+ ! ts . isIdentifier ( parent . name )
402+ ) {
403+ return false ;
404+ }
405+
406+ const declaration = reflection . getDeclarationOfIdentifier ( node ) ;
407+ return (
408+ declaration !== null &&
409+ ts . isEnumDeclaration ( declaration . node ) &&
410+ ! ! declaration . node . modifiers ?. some ( ( m ) => m . kind === ts . SyntaxKind . ConstKeyword )
411+ ) ;
412+ }
0 commit comments