Skip to content

Commit 2894222

Browse files
committed
Merge branch 'sourcemap'
2 parents 106d66b + 272d8b6 commit 2894222

File tree

16 files changed

+535
-132
lines changed

16 files changed

+535
-132
lines changed

src/React.Sample.Mvc4/Content/Sample.jsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,21 +29,21 @@ var CommentsBox = React.createClass({
2929
var url = evt.target.href;
3030
var xhr = new XMLHttpRequest();
3131
xhr.open('GET', url, true);
32-
xhr.onload = function() {
32+
xhr.onload = () => {
3333
var data = JSON.parse(xhr.responseText);
3434
this.setState({
3535
comments: this.state.comments.concat(data.comments),
3636
hasMore: data.hasMore,
3737
loadingMore: false
3838
});
39-
}.bind(this);
39+
};
4040
xhr.send();
4141
return false;
4242
},
4343
render() {
44-
var commentNodes = this.state.comments.map(function (comment) {
45-
return <Comment author={comment.Author}>{comment.Text}</Comment>;
46-
});
44+
var commentNodes = this.state.comments.map(comment =>
45+
<Comment author={comment.Author}>{comment.Text}</Comment>
46+
);
4747

4848
return (
4949
<div className="comments">

src/React.Tests/Core/JsxTransformerTests.cs

Lines changed: 25 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
*/
99

1010
using System;
11-
using System.Collections.Generic;
1211
using Moq;
1312
using NUnit.Framework;
1413
using React.Exceptions;
@@ -21,7 +20,7 @@ public class JsxTransformerTests
2120
private Mock<IReactEnvironment> _environment;
2221
private Mock<ICache> _cache;
2322
private Mock<IFileSystem> _fileSystem;
24-
private Mock<IFileCacheHash> _fileCacheHash;
23+
private Mock<IFileCacheHash> _fileCacheHash;
2524
private JsxTransformer _jsxTransformer;
2625

2726
[SetUp]
@@ -85,13 +84,10 @@ public void ShouldThrowIfEngineNotSupported()
8584
[Test]
8685
public void ShouldUseCacheProvider()
8786
{
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 */");
87+
_cache.Setup(x => x.Get<JavaScriptWithSourceMap>("JSX_v2_foo.jsx", null)).Returns(new JavaScriptWithSourceMap
88+
{
89+
Code = "/* cached */"
90+
});
9591

9692
var result = _jsxTransformer.TransformJsxFile("foo.jsx");
9793
Assert.AreEqual("/* cached */", result);
@@ -104,7 +100,7 @@ public void ShouldUseFileSystemCacheIfHashValid()
104100
_fileSystem.Setup(x => x.FileExists("foo.generated.js")).Returns(true);
105101
_fileSystem.Setup(x => x.ReadAsString("foo.generated.js")).Returns("/* filesystem cached */");
106102
_fileCacheHash.Setup(x => x.ValidateHash(It.IsAny<string>(), It.IsAny<string>())).Returns(true);
107-
103+
108104
var result = _jsxTransformer.TransformJsxFile("foo.jsx");
109105
Assert.AreEqual("/* filesystem cached */", result);
110106
}
@@ -117,13 +113,14 @@ public void ShouldTransformJsxIfFileCacheHashInvalid()
117113
_fileSystem.Setup(x => x.ReadAsString("foo.generated.js")).Returns("/* filesystem cached invalid */");
118114
_fileSystem.Setup(x => x.ReadAsString("foo.jsx")).Returns("<div>Hello World</div>");
119115
_fileCacheHash.Setup(x => x.ValidateHash(It.IsAny<string>(), It.IsAny<string>())).Returns(false);
120-
121-
_jsxTransformer.TransformJsxFile("foo.jsx");
122-
_environment.Verify(x => x.ExecuteWithLargerStackIfRequired<string>(
123-
"ReactNET_transform",
124-
"<div>Hello World</div>",
116+
_environment.Setup(x => x.ExecuteWithLargerStackIfRequired<JavaScriptWithSourceMap>(
117+
"ReactNET_transform_sourcemap",
118+
It.IsAny<string>(),
125119
false
126-
));
120+
)).Returns(new JavaScriptWithSourceMap { Code = "React.DOM.div('Hello World')" });
121+
122+
var result = _jsxTransformer.TransformJsxFile("foo.jsx");
123+
Assert.AreEqual("React.DOM.div('Hello World')", result);
127124
}
128125

129126
[Test]
@@ -132,24 +129,25 @@ public void ShouldTransformJsxIfNoCache()
132129
SetUpEmptyCache();
133130
_fileSystem.Setup(x => x.FileExists("foo.generated.js")).Returns(false);
134131
_fileSystem.Setup(x => x.ReadAsString("foo.jsx")).Returns("<div>Hello World</div>");
135-
136-
_jsxTransformer.TransformJsxFile("foo.jsx");
137-
_environment.Verify(x => x.ExecuteWithLargerStackIfRequired<string>(
138-
"ReactNET_transform",
139-
"<div>Hello World</div>",
132+
_environment.Setup(x => x.ExecuteWithLargerStackIfRequired<JavaScriptWithSourceMap>(
133+
"ReactNET_transform_sourcemap",
134+
It.IsAny<string>(),
140135
false
141-
));
136+
)).Returns(new JavaScriptWithSourceMap { Code = "React.DOM.div('Hello World')" });
137+
138+
var result = _jsxTransformer.TransformJsxFile("foo.jsx");
139+
Assert.AreEqual("React.DOM.div('Hello World')", result);
142140
}
143141

144142
[Test]
145143
public void ShouldSaveTransformationResult()
146144
{
147145
_fileSystem.Setup(x => x.ReadAsString("foo.jsx")).Returns("<div>Hello World</div>");
148-
_environment.Setup(x => x.ExecuteWithLargerStackIfRequired<string>(
149-
"ReactNET_transform",
150-
"<div>Hello World</div>",
146+
_environment.Setup(x => x.ExecuteWithLargerStackIfRequired<JavaScriptWithSourceMap>(
147+
"ReactNET_transform_sourcemap",
148+
It.IsAny<string>(),
151149
false
152-
)).Returns("React.DOM.div('Hello World')");
150+
)).Returns(new JavaScriptWithSourceMap { Code = "React.DOM.div('Hello World')" });
153151

154152
string result = null;
155153
_fileSystem.Setup(x => x.WriteAsString("foo.generated.js", It.IsAny<string>())).Callback(
@@ -163,20 +161,7 @@ public void ShouldSaveTransformationResult()
163161

164162
private void SetUpEmptyCache()
165163
{
166-
_cache.Setup(x => x.GetOrInsert(
167-
/*key*/ "JSX_foo.jsx",
168-
/*slidingExpiration*/ It.IsAny<TimeSpan>(),
169-
/*getData*/ It.IsAny<Func<string>>(),
170-
/*cacheDependencyKeys*/ It.IsAny<IEnumerable<string>>(),
171-
/*cacheDependencyFiles*/ It.IsAny<IEnumerable<string>>()
172-
))
173-
.Returns((
174-
string key,
175-
TimeSpan slidingExpiration,
176-
Func<string> getData,
177-
IEnumerable<string> cacheDependencyFiles,
178-
IEnumerable<string> cacheDependencyKeys
179-
) => getData());
164+
_cache.Setup(x => x.Get<JavaScriptWithSourceMap>("JSX_v2_foo.jsx", null)).Returns((JavaScriptWithSourceMap)null);
180165
}
181166
}
182167
}

src/React.Web/AspNetCache.cs

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,28 @@ public AspNetCache(HttpContextBase context)
3535
}
3636

3737
/// <summary>
38-
/// Get an item from the cache. If it doesn't exist, call the function to load it
38+
/// Get an item from the cache. Returns <paramref name="fallback"/> if the item does
39+
/// not exist.
3940
/// </summary>
4041
/// <typeparam name="T">Type of data</typeparam>
41-
/// <param name="key">The cache key.</param>
42+
/// <param name="key">The cache key</param>
43+
/// <param name="fallback">Value to return if item is not in the cache</param>
44+
/// <returns>Data from cache, otherwise <paramref name="fallback"/></returns>
45+
public T Get<T>(string key, T fallback = default(T))
46+
{
47+
return (T)(_cache[key] ?? fallback);
48+
}
49+
50+
/// <summary>
51+
/// Sets an item in the cache.
52+
/// </summary>
53+
/// <typeparam name="T">Type of data</typeparam>
54+
/// <param name="key">The cache key</param>
55+
/// <param name="data">Data to cache</param>
4256
/// <param name="slidingExpiration">
4357
/// Sliding expiration, if cache key is not accessed in this time period it will
4458
/// automatically be removed from the cache
4559
/// </param>
46-
/// <param name="getData">Function to load data to cache. Called if data isn't in the cache, or is stale</param>
4760
/// <param name="cacheDependencyFiles">
4861
/// Filenames this cached item is dependent on. If any of these files change, the cache
4962
/// will be cleared automatically
@@ -52,31 +65,18 @@ public AspNetCache(HttpContextBase context)
5265
/// Other cache keys this cached item is dependent on. If any of these keys change, the
5366
/// cache will be cleared automatically
5467
/// </param>
55-
/// <returns>Data</returns>
56-
public T GetOrInsert<T>(
57-
string key,
58-
TimeSpan slidingExpiration,
59-
Func<T> getData,
68+
public void Set<T>(
69+
string key, T data,
70+
TimeSpan slidingExpiration,
6071
IEnumerable<string> cacheDependencyFiles = null,
6172
IEnumerable<string> cacheDependencyKeys = null
6273
)
6374
{
64-
// Check for data in cache
65-
var data = (T)(_cache[key] ?? default(T));
66-
67-
// http://stackoverflow.com/questions/65351/null-or-default-comparsion-of-generic-argument-in-c-sharp
68-
if (object.Equals(data, default(T)))
69-
{
70-
// Load data and save into cache
71-
data = getData();
72-
var cacheDependency = new CacheDependency(
73-
(cacheDependencyFiles ?? Enumerable.Empty<string>()).ToArray(),
74-
(cacheDependencyKeys ?? Enumerable.Empty<string>()).ToArray()
75-
);
76-
_cache.Insert(key, data, cacheDependency, Cache.NoAbsoluteExpiration, slidingExpiration);
77-
}
78-
79-
return data;
75+
var cacheDependency = new CacheDependency(
76+
(cacheDependencyFiles ?? Enumerable.Empty<string>()).ToArray(),
77+
(cacheDependencyKeys ?? Enumerable.Empty<string>()).ToArray()
78+
);
79+
_cache.Insert(key, data, cacheDependency, Cache.NoAbsoluteExpiration, slidingExpiration);
8080
}
8181
}
8282
}

src/React.Web/JsxHandler.cs

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
*/
99

1010
using System.Web;
11-
using System.Web.Caching;
1211

1312
namespace React.Web
1413
{
@@ -46,18 +45,75 @@ HttpResponseBase response
4645
/// Executes the handler. Outputs JavaScript to the response.
4746
/// </summary>
4847
public void Execute()
48+
{
49+
if (_request.QueryString["map"] != null)
50+
{
51+
RenderSourceMap();
52+
}
53+
else
54+
{
55+
RenderJsxFile();
56+
}
57+
}
58+
59+
/// <summary>
60+
/// Renders the JSX file converted to regular JavaScript.
61+
/// </summary>
62+
private void RenderJsxFile()
4963
{
5064
var relativePath = _request.Url.LocalPath;
51-
var result = _environment.JsxTransformer.TransformJsxFile(relativePath);
65+
var result = _environment.JsxTransformer.TransformJsxFileWithSourceMap(relativePath);
66+
var sourceMapUri = GetSourceMapUri(relativePath, result.Hash);
67+
ConfigureCaching();
68+
_response.ContentType = "text/javascript";
69+
// The sourcemap spec says to use SourceMap, but Firefox only accepts X-SourceMap
70+
_response.AddHeader("SourceMap", sourceMapUri);
71+
_response.AddHeader("X-SourceMap", sourceMapUri);
5272

53-
// Only cache on the server-side for now
54-
_response.AddFileDependency(_fileSystem.MapPath(relativePath));
73+
_response.Write(result.Code);
74+
}
75+
76+
/// <summary>
77+
/// Renders the source map for this JSX file.
78+
/// </summary>
79+
private void RenderSourceMap()
80+
{
81+
var relativePath = _request.Url.LocalPath;
82+
var result = _environment.JsxTransformer.TransformJsxFileWithSourceMap(relativePath, forceGenerateSourceMap: true);
83+
if (result.SourceMap == null)
84+
{
85+
_response.StatusCode = 500;
86+
_response.StatusDescription = "Unable to generate source map";
87+
return;
88+
}
89+
var sourceMap = result.SourceMap.ToJson();
90+
91+
ConfigureCaching();
92+
_response.ContentType = "application/json";
93+
//_response.Write(")]}\n"); // Recommended by the spec but Firefox doesn't support it
94+
_response.Write(sourceMap);
95+
}
96+
97+
/// <summary>
98+
/// Send headers to cache the response. Only caches on the server-side for now
99+
/// </summary>
100+
private void ConfigureCaching()
101+
{
102+
_response.AddFileDependency(_fileSystem.MapPath(_request.Url.LocalPath));
55103
_response.Cache.SetCacheability(HttpCacheability.Server);
56104
_response.Cache.SetLastModifiedFromFileDependencies();
57105
_response.Cache.SetETagFromFileDependencies();
106+
}
58107

59-
_response.ContentType = "text/javascript";
60-
_response.Write(result);
108+
/// <summary>
109+
/// Gets the URI to the source map of the specified file
110+
/// </summary>
111+
/// <param name="relativePath">Relative path to the JavaScript file</param>
112+
/// <param name="hash">Hash of the file</param>
113+
/// <returns>URI to the file</returns>
114+
private static string GetSourceMapUri(string relativePath, string hash)
115+
{
116+
return string.Format("{0}?map={1}", relativePath, hash);
61117
}
62118
}
63119
}

src/React.Web/React.Web.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@
4646
<Private>True</Private>
4747
<HintPath>..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll</HintPath>
4848
</Reference>
49+
<Reference Include="Newtonsoft.Json, Version=4.5.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
50+
<HintPath>..\packages\Newtonsoft.Json.5.0.4\lib\net40\Newtonsoft.Json.dll</HintPath>
51+
</Reference>
4952
<Reference Include="System" />
5053
<Reference Include="System.Core" />
5154
<Reference Include="Microsoft.CSharp" />

src/React.Web/packages.config

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<packages>
33
<package id="Microsoft.Web.Infrastructure" version="1.0.0.0" targetFramework="net40" />
4+
<package id="Newtonsoft.Json" version="5.0.4" targetFramework="net40" />
45
<package id="WebActivatorEx" version="2.0.5" targetFramework="net40" />
56
</packages>

src/React/FileCacheHash.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class FileCacheHash : IFileCacheHash
2121
/// <summary>
2222
/// Prefix used for hash line in transformed file. Used for caching.
2323
/// </summary>
24-
internal const string HASH_PREFIX = "// @hash ";
24+
internal const string HASH_PREFIX = "// @hash v2-";
2525

2626
/// <summary>
2727
/// Althorithm for calculating file hashes

src/React/ICache.cs

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,25 @@ namespace React
1818
public interface ICache
1919
{
2020
/// <summary>
21-
/// Get an item from the cache. If it doesn't exist, call the function to load it
21+
/// Get an item from the cache. Returns <paramref name="fallback"/> if the item does
22+
/// not exist.
2223
/// </summary>
2324
/// <typeparam name="T">Type of data</typeparam>
24-
/// <param name="key">The cache key.</param>
25+
/// <param name="key">The cache key</param>
26+
/// <param name="fallback">Value to return if item is not in the cache</param>
27+
/// <returns>Data from cache, otherwise <paramref name="fallback"/></returns>
28+
T Get<T>(string key, T fallback = default(T));
29+
30+
/// <summary>
31+
/// Sets an item in the cache.
32+
/// </summary>
33+
/// <typeparam name="T">Type of data</typeparam>
34+
/// <param name="key">The cache key</param>
35+
/// <param name="data">Data to cache</param>
2536
/// <param name="slidingExpiration">
2637
/// Sliding expiration, if cache key is not accessed in this time period it will
2738
/// automatically be removed from the cache
2839
/// </param>
29-
/// <param name="getData">Function to load data to cache. Called if data isn't in the cache, or is stale</param>
3040
/// <param name="cacheDependencyFiles">
3141
/// Filenames this cached item is dependent on. If any of these files change, the cache
3242
/// will be cleared automatically
@@ -35,11 +45,10 @@ public interface ICache
3545
/// Other cache keys this cached item is dependent on. If any of these keys change, the
3646
/// cache will be cleared automatically
3747
/// </param>
38-
/// <returns>Data</returns>
39-
T GetOrInsert<T>(
40-
string key,
48+
void Set<T>(
49+
string key,
50+
T data,
4151
TimeSpan slidingExpiration,
42-
Func<T> getData,
4352
IEnumerable<string> cacheDependencyFiles = null,
4453
IEnumerable<string> cacheDependencyKeys = null
4554
);

0 commit comments

Comments
 (0)