8
8
*/
9
9
10
10
using System ;
11
- using System . Diagnostics ;
12
11
using System . IO ;
12
+ using System . Security . Cryptography ;
13
+ using System . Text ;
13
14
using Newtonsoft . Json ;
14
15
using React . Exceptions ;
15
16
@@ -28,6 +29,10 @@ public class JsxTransformer : IJsxTransformer
28
29
/// Suffix to append to compiled files
29
30
/// </summary>
30
31
private const string COMPILED_FILE_SUFFIX = ".generated.js" ;
32
+ /// <summary>
33
+ /// Prefix used for hash line in transformed file. Used for caching.
34
+ /// </summary>
35
+ private const string HASH_PREFIX = "// @hash " ;
31
36
32
37
/// <summary>
33
38
/// Environment this JSX Transformer has been created in
@@ -41,6 +46,10 @@ public class JsxTransformer : IJsxTransformer
41
46
/// File system wrapper
42
47
/// </summary>
43
48
private readonly IFileSystem _fileSystem ;
49
+ /// <summary>
50
+ /// Althorithm for calculating file hashes
51
+ /// </summary>
52
+ private readonly HashAlgorithm _hash = MD5 . Create ( ) ;
44
53
45
54
/// <summary>
46
55
/// Initializes a new instance of the <see cref="JsxTransformer"/> class.
@@ -72,29 +81,40 @@ public string TransformJsxFile(string filename)
72
81
getData : ( ) =>
73
82
{
74
83
// 2. Check on-disk cache
75
- var cachePath = GetJsxOutputPath ( filename ) ;
76
- if ( _fileSystem . FileExists ( cachePath ) )
84
+ var contents = _fileSystem . ReadAsString ( filename ) ;
85
+ var hash = CalculateHash ( contents ) ;
86
+
87
+ var cacheFilename = GetJsxOutputPath ( filename ) ;
88
+ if ( _fileSystem . FileExists ( cacheFilename ) )
77
89
{
78
- // TODO: Checksum to ensure file hasn't changed
79
- return _fileSystem . ReadAsString ( cachePath ) ;
90
+ var cacheContents = _fileSystem . ReadAsString ( cacheFilename ) ;
91
+ if ( ValidateHash ( cacheContents , hash ) )
92
+ {
93
+ // Cache is valid :D
94
+ return cacheContents ;
95
+ }
80
96
}
81
97
82
98
// 3. Not cached, perform the transformation
83
- return TransformJsxFileWithoutCache ( filename ) ;
99
+ return TransformJsxWithHeader ( contents , hash ) ;
84
100
}
85
101
) ;
86
102
}
87
103
88
104
/// <summary>
89
- /// Transforms a JSX file without checking if a cached version exists. For most purposes,
90
- /// you'll be better off using <see cref="TransformJsxFile" /> .
105
+ /// Transforms JSX into regular JavaScript, and prepends a header used for caching
106
+ /// purposes .
91
107
/// </summary>
92
- /// <param name="filename">Name of the file to transform</param>
108
+ /// <param name="contents">Contents of the input file</param>
109
+ /// <param name="hash">Hash of the input. If null, it will be calculated</param>
93
110
/// <returns>JavaScript</returns>
94
- public string TransformJsxFileWithoutCache ( string filename )
111
+ private string TransformJsxWithHeader ( string contents , string hash = null )
95
112
{
96
- var contents = _fileSystem . ReadAsString ( filename ) ;
97
- return TransformJsx ( contents ) ;
113
+ if ( string . IsNullOrEmpty ( hash ) )
114
+ {
115
+ hash = CalculateHash ( contents ) ;
116
+ }
117
+ return GetFileHeader ( hash ) + TransformJsx ( contents ) ;
98
118
}
99
119
100
120
/// <summary>
@@ -127,6 +147,55 @@ public string TransformJsx(string input)
127
147
}
128
148
}
129
149
150
+ /// <summary>
151
+ /// Calculates a hash for the specified input
152
+ /// </summary>
153
+ /// <param name="input">Input string</param>
154
+ /// <returns>Hash of the input</returns>
155
+ private string CalculateHash ( string input )
156
+ {
157
+ var inputBytes = Encoding . UTF8 . GetBytes ( input ) ;
158
+ var hash = _hash . ComputeHash ( inputBytes ) ;
159
+ return BitConverter . ToString ( hash ) . Replace ( "-" , string . Empty ) ;
160
+ }
161
+
162
+ /// <summary>
163
+ /// Validates that the cache's hash is valid. This is used to ensure the input has not
164
+ /// changed, and to invalidate the cache if so.
165
+ /// </summary>
166
+ /// <param name="cacheContents">Contents retrieved from cache</param>
167
+ /// <param name="hash">Hash of the input</param>
168
+ /// <returns><c>true</c> if the cache is still valid</returns>
169
+ private bool ValidateHash ( string cacheContents , string hash )
170
+ {
171
+ // Check if first line is hash
172
+ var firstLineBreak = cacheContents . IndexOfAny ( new [ ] { '\r ' , '\n ' } ) ;
173
+ var firstLine = cacheContents . Substring ( 0 , firstLineBreak ) ;
174
+ if ( ! firstLine . StartsWith ( HASH_PREFIX ) )
175
+ {
176
+ // Cache doesn't have hash - Err on the side of caution and invalidate it.
177
+ return false ;
178
+ }
179
+ var cacheHash = firstLine . Replace ( HASH_PREFIX , string . Empty ) ;
180
+ return cacheHash == hash ;
181
+ }
182
+
183
+ /// <summary>
184
+ /// Gets the header prepended to JSX transformed files. Contains a hash that is used to
185
+ /// validate the cache.
186
+ /// </summary>
187
+ /// <param name="hash">Hash of the input</param>
188
+ /// <returns>Header for the cache</returns>
189
+ private string GetFileHeader ( string hash )
190
+ {
191
+ return string . Format (
192
+ @"{0}{1}
193
+ // Automatically generated by ReactJS.NET. Do not edit, your changes will be overridden.
194
+ // Version: {2}
195
+ ///////////////////////////////////////////////////////////////////////////////
196
+ " , HASH_PREFIX , hash , _environment . Version ) ;
197
+ }
198
+
130
199
/// <summary>
131
200
/// Returns the path the specified JSX file's compilation will be cached to if
132
201
/// <see cref="TransformAndSaveJsxFile" /> is called.
@@ -150,7 +219,8 @@ public string GetJsxOutputPath(string path)
150
219
public string TransformAndSaveJsxFile ( string filename )
151
220
{
152
221
var outputPath = GetJsxOutputPath ( filename ) ;
153
- var result = TransformJsxFileWithoutCache ( filename ) ;
222
+ var contents = _fileSystem . ReadAsString ( filename ) ;
223
+ var result = TransformJsxWithHeader ( contents ) ;
154
224
_fileSystem . WriteAsString ( outputPath , result ) ;
155
225
return outputPath ;
156
226
}
0 commit comments