Skip to content

Commit 9f1b742

Browse files
committed
Use on-disk JSX transformation cache (from MSBuild task) if it exists.
1 parent ae9e07e commit 9f1b742

File tree

5 files changed

+165
-39
lines changed

5 files changed

+165
-39
lines changed

src/React.Tests/Core/JsxTransformerTests.cs

Lines changed: 111 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
* of patent rights can be found in the PATENTS file in the same directory.
88
*/
99

10+
using System;
11+
using System.Collections.Generic;
1012
using Moq;
1113
using NUnit.Framework;
1214
using React.Exceptions;
@@ -16,59 +18,138 @@ namespace React.Tests.Core
1618
[TestFixture]
1719
public class JsxTransformerTests
1820
{
21+
private Mock<IReactEnvironment> _environment;
22+
private Mock<ICache> _cache;
23+
private Mock<IFileSystem> _fileSystem;
24+
private JsxTransformer _jsxTransformer;
25+
26+
[SetUp]
27+
public void SetUp()
28+
{
29+
_environment = new Mock<IReactEnvironment>();
30+
_environment.Setup(x => x.EngineSupportsJsxTransformer).Returns(true);
31+
32+
_cache = new Mock<ICache>();
33+
_fileSystem = new Mock<IFileSystem>();
34+
_fileSystem.Setup(x => x.MapPath(It.IsAny<string>())).Returns<string>(x => x);
35+
36+
_jsxTransformer = new JsxTransformer(
37+
_environment.Object,
38+
_cache.Object,
39+
_fileSystem.Object
40+
);
41+
}
42+
1943
[Test]
2044
public void ShouldNotTransformJsxIfNoAnnotationPresent()
2145
{
22-
var environment = new Mock<IReactEnvironment>();
23-
var cache = new Mock<ICache>();
24-
var fileSystem = new Mock<IFileSystem>();
25-
var jsxTransformer = new JsxTransformer(
26-
environment.Object,
27-
cache.Object,
28-
fileSystem.Object
29-
);
3046
const string input = "<div>Hello World</div>";
3147

32-
var output = jsxTransformer.TransformJsx(input);
48+
var output = _jsxTransformer.TransformJsx(input);
3349
Assert.AreEqual(input, output);
3450
}
3551

3652
[Test]
3753
public void ShouldTransformJsxIfAnnotationPresent()
3854
{
39-
var environment = new Mock<IReactEnvironment>();
40-
var cache = new Mock<ICache>();
41-
var fileSystem = new Mock<IFileSystem>();
42-
var jsxTransformer = new JsxTransformer(
43-
environment.Object,
44-
cache.Object,
45-
fileSystem.Object
46-
);
47-
environment.Setup(x => x.EngineSupportsJsxTransformer).Returns(true);
4855
const string input = "/** @jsx React.DOM */ <div>Hello World</div>";
49-
jsxTransformer.TransformJsx(input);
56+
_jsxTransformer.TransformJsx(input);
5057

51-
environment.Verify(x => x.ExecuteWithLargerStackIfRequired<string>(
58+
_environment.Verify(x => x.ExecuteWithLargerStackIfRequired<string>(
5259
@"global.JSXTransformer.transform(""/** @jsx React.DOM */ <div>Hello World</div>"").code"
5360
));
5461
}
5562

63+
[Test]
64+
public void ShouldWrapExceptionsInJsxExeption()
65+
{
66+
_environment.Setup(x => x.ExecuteWithLargerStackIfRequired<string>(
67+
@"global.JSXTransformer.transform(""/** @jsx React.DOM */ <div>Hello World</div>"").code"
68+
)).Throws(new Exception("Something broke..."));
69+
70+
const string input = "/** @jsx React.DOM */ <div>Hello World</div>";
71+
Assert.Throws<JsxException>(() => _jsxTransformer.TransformJsx(input));
72+
}
73+
74+
[Test]
5675
public void ShouldThrowIfEngineNotSupported()
5776
{
58-
var environment = new Mock<IReactEnvironment>();
59-
var cache = new Mock<ICache>();
60-
var fileSystem = new Mock<IFileSystem>();
61-
var jsxTransformer = new JsxTransformer(
62-
environment.Object,
63-
cache.Object,
64-
fileSystem.Object
65-
);
66-
environment.Setup(x => x.EngineSupportsJsxTransformer).Returns(false);
77+
_environment.Setup(x => x.EngineSupportsJsxTransformer).Returns(false);
6778

6879
Assert.Throws<JsxUnsupportedEngineException>(() =>
6980
{
70-
jsxTransformer.TransformJsx("/** @jsx React.DOM */ <div>Hello world</div>");
81+
_jsxTransformer.TransformJsx("/** @jsx React.DOM */ <div>Hello world</div>");
7182
});
7283
}
84+
85+
[Test]
86+
public void ShouldUseCacheProvider()
87+
{
88+
_cache.Setup(x => x.GetOrInsert(
89+
/*key*/ "JSX_foo.jsx",
90+
/*slidingExpiration*/ It.IsAny<TimeSpan>(),
91+
/*getData*/ It.IsAny<Func<string>>(),
92+
/*cacheDependencyKeys*/ It.IsAny<IEnumerable<string>>(),
93+
/*cacheDependencyFiles*/ It.IsAny<IEnumerable<string>>()
94+
)).Returns("/* cached */");
95+
96+
var result = _jsxTransformer.TransformJsxFile("foo.jsx");
97+
Assert.AreEqual("/* cached */", result);
98+
}
99+
100+
[Test]
101+
public void ShouldUseFileSystemCache()
102+
{
103+
SetUpEmptyCache();
104+
_fileSystem.Setup(x => x.FileExists("foo.generated.js")).Returns(true);
105+
_fileSystem.Setup(x => x.ReadAsString("foo.generated.js")).Returns("/* filesystem cached */");
106+
107+
var result = _jsxTransformer.TransformJsxFile("foo.jsx");
108+
Assert.AreEqual("/* filesystem cached */", result);
109+
}
110+
111+
[Test]
112+
public void ShouldTransformJsxIfNoCache()
113+
{
114+
SetUpEmptyCache();
115+
_fileSystem.Setup(x => x.FileExists("foo.generated.js")).Returns(false);
116+
_fileSystem.Setup(x => x.ReadAsString("foo.jsx")).Returns("/** @jsx React.DOM */ <div>Hello World</div>");
117+
118+
_jsxTransformer.TransformJsxFile("foo.jsx");
119+
_environment.Verify(x => x.ExecuteWithLargerStackIfRequired<string>(
120+
@"global.JSXTransformer.transform(""/** @jsx React.DOM */ <div>Hello World</div>"").code"
121+
));
122+
}
123+
124+
[Test]
125+
public void ShouldSaveTransformationResult()
126+
{
127+
_fileSystem.Setup(x => x.ReadAsString("foo.jsx")).Returns("/** @jsx React.DOM */ <div>Hello World</div>");
128+
_environment.Setup(x => x.ExecuteWithLargerStackIfRequired<string>(
129+
@"global.JSXTransformer.transform(""/** @jsx React.DOM */ <div>Hello World</div>"").code"
130+
)).Returns("React.DOM.div('Hello World')");
131+
132+
var result = _jsxTransformer.TransformAndSaveJsxFile("foo.jsx");
133+
Assert.AreEqual("foo.generated.js", result);
134+
_fileSystem.Verify(x => x.WriteAsString("foo.generated.js", "React.DOM.div('Hello World')"));
135+
}
136+
137+
private void SetUpEmptyCache()
138+
{
139+
_cache.Setup(x => x.GetOrInsert(
140+
/*key*/ "JSX_foo.jsx",
141+
/*slidingExpiration*/ It.IsAny<TimeSpan>(),
142+
/*getData*/ It.IsAny<Func<string>>(),
143+
/*cacheDependencyKeys*/ It.IsAny<IEnumerable<string>>(),
144+
/*cacheDependencyFiles*/ It.IsAny<IEnumerable<string>>()
145+
))
146+
.Returns((
147+
string key,
148+
TimeSpan slidingExpiration,
149+
Func<string> getData,
150+
IEnumerable<string> cacheDependencyFiles,
151+
IEnumerable<string> cacheDependencyKeys
152+
) => getData());
153+
}
73154
}
74155
}

src/React/FileSystemBase.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,15 @@ public void WriteAsString(string relativePath, string contents)
4343
{
4444
File.WriteAllText(MapPath(relativePath), contents, Encoding.UTF8);
4545
}
46+
47+
/// <summary>
48+
/// Determines if the specified file exists
49+
/// </summary>
50+
/// <param name="relativePath">App-relative path of the file</param>
51+
/// <returns><c>true</c> if the file exists</returns>
52+
public bool FileExists(string relativePath)
53+
{
54+
return File.Exists(MapPath(relativePath));
55+
}
4656
}
4757
}

src/React/IFileSystem.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,12 @@ public interface IFileSystem
3434
/// <param name="relativePath">App-relative path of the file</param>
3535
/// <param name="contents">Contents of the file</param>
3636
void WriteAsString(string relativePath, string contents);
37+
38+
/// <summary>
39+
/// Determines if the specified file exists
40+
/// </summary>
41+
/// <param name="relativePath">App-relative path of the file</param>
42+
/// <returns><c>true</c> if the file exists</returns>
43+
bool FileExists(string relativePath);
3744
}
3845
}

src/React/IJsxTransformer.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,23 @@ namespace React
1515
public interface IJsxTransformer
1616
{
1717
/// <summary>
18-
/// Loads a JSX file. Results of the JSX to JavaScript transformation are cached.
18+
/// Transforms a JSX file. Results of the JSX to JavaScript transformation are cached.
1919
/// </summary>
2020
/// <param name="filename">Name of the file to load</param>
21-
/// <returns>File contents</returns>
21+
/// <returns>JavaScript</returns>
2222
string TransformJsxFile(string filename);
2323

24+
/// <summary>
25+
/// Transforms a JSX file without checking if a cached version exists. For most purposes,
26+
/// you'll be better off using <see cref="TransformJsxFile" />.
27+
/// </summary>
28+
/// <param name="filename">Name of the file to transform</param>
29+
/// <returns>JavaScript</returns>
30+
string TransformJsxFileWithoutCache(string filename);
31+
2432
/// <summary>
2533
/// Transforms JSX into regular JavaScript. The result is not cached. Use
26-
/// <see cref="JsxTransformer.TransformJsxFile"/> if loading from a file since this will cache the result.
34+
/// <see cref="TransformJsxFile"/> if loading from a file since this will cache the result.
2735
/// </summary>
2836
/// <param name="input">JSX</param>
2937
/// <returns>JavaScript</returns>

src/React/JsxTransformer.cs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,27 +56,47 @@ public JsxTransformer(IReactEnvironment environment, ICache cache, IFileSystem f
5656
}
5757

5858
/// <summary>
59-
/// Loads a JSX file. Results of the JSX to JavaScript transformation are cached.
59+
/// Transforms a JSX file. Results of the JSX to JavaScript transformation are cached.
6060
/// </summary>
6161
/// <param name="filename">Name of the file to load</param>
62-
/// <returns>File contents</returns>
62+
/// <returns>JavaScript</returns>
6363
public string TransformJsxFile(string filename)
6464
{
6565
var fullPath = _fileSystem.MapPath(filename);
6666

67+
// 1. Check in-memory cache
6768
return _cache.GetOrInsert(
6869
key: string.Format(JSX_CACHE_KEY, filename),
6970
slidingExpiration: TimeSpan.FromMinutes(30),
7071
cacheDependencyFiles: new[] { fullPath },
7172
getData: () =>
7273
{
73-
Trace.WriteLine(string.Format("Parsing JSX from {0}", filename));
74-
var contents = _fileSystem.ReadAsString(filename);
75-
return TransformJsx(contents);
74+
// 2. Check on-disk cache
75+
var cachePath = GetJsxOutputPath(filename);
76+
if (_fileSystem.FileExists(cachePath))
77+
{
78+
// TODO: Checksum to ensure file hasn't changed
79+
return _fileSystem.ReadAsString(cachePath);
80+
}
81+
82+
// 3. Not cached, perform the transformation
83+
return TransformJsxFileWithoutCache(filename);
7684
}
7785
);
7886
}
7987

88+
/// <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" />.
91+
/// </summary>
92+
/// <param name="filename">Name of the file to transform</param>
93+
/// <returns>JavaScript</returns>
94+
public string TransformJsxFileWithoutCache(string filename)
95+
{
96+
var contents = _fileSystem.ReadAsString(filename);
97+
return TransformJsx(contents);
98+
}
99+
80100
/// <summary>
81101
/// Transforms JSX into regular JavaScript. The result is not cached. Use
82102
/// <see cref="TransformJsxFile"/> if loading from a file since this will cache the result.
@@ -130,7 +150,7 @@ public string GetJsxOutputPath(string path)
130150
public string TransformAndSaveJsxFile(string filename)
131151
{
132152
var outputPath = GetJsxOutputPath(filename);
133-
var result = TransformJsxFile(filename);
153+
var result = TransformJsxFileWithoutCache(filename);
134154
_fileSystem.WriteAsString(outputPath, result);
135155
return outputPath;
136156
}

0 commit comments

Comments
 (0)