Skip to content

Commit 1c03b00

Browse files
author
Robert Mosolgo
committed
Merge pull request #273 from rmosolgo/jsx-refactor
refactor(JSX::Transformer) use Transformer class; allow asset_path config
2 parents 1e5d99a + ec5925b commit 1c03b00

File tree

7 files changed

+110
-46
lines changed

7 files changed

+110
-46
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ You can use JSX `--harmony` or `--strip-types` options by adding a configuration
8181
config.react.jsx_transform_options = {
8282
harmony: true,
8383
strip_types: true, # for removing Flow type annotations
84+
asset_path: "path/to/JSXTransformer.js", # if your JSXTransformer is somewhere else
8485
}
8586
```
8687

lib/react/jsx.rb

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,28 @@
11
require 'execjs'
22
require 'react/jsx/template'
3+
require 'react/jsx/transformer'
34
require 'rails'
45

56
module React
67
module JSX
7-
mattr_accessor :transform_options
8+
mattr_accessor :transform_options, :transformer_class
89

9-
def self.context
10-
# lazily loaded during first request and reloaded every time when in dev or test
11-
unless @context && ::Rails.env.production?
12-
contents =
13-
# If execjs uses therubyracer, there is no 'global'. Make sure
14-
# we have it so JSX script can work properly.
15-
'var global = global || this;' +
16-
17-
# search for transformer file using sprockets - allows user to override
18-
# this file in his own application
19-
File.read(::Rails.application.assets.resolve('JSXTransformer.js'))
20-
21-
@context = ExecJS.compile(contents)
22-
end
10+
# You can assign `React::JSX.transformer_class = `
11+
# to provide your own transformer. It must implement:
12+
# - #initialize(options)
13+
# - #transform(code) => new code
14+
self.transformer_class = Transformer
2315

24-
@context
16+
def self.transform(code)
17+
transformer.transform(code)
2518
end
2619

27-
def self.transform(code, options={})
28-
js_options = {
29-
stripTypes: options[:strip_types],
30-
harmony: options[:harmony],
31-
}
32-
result = context.call('JSXTransformer.transform', code, js_options)
33-
return result['code']
20+
def self.transformer
21+
# lazily loaded during first request and reloaded every time when in dev or test
22+
if @transformer.nil? || !::Rails.env.production?
23+
@transformer = transformer_class.new(transform_options)
24+
end
25+
@transformer
3426
end
3527
end
3628
end

lib/react/jsx/template.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ def prepare
1010
end
1111

1212
def evaluate(scope, locals, &block)
13-
@output ||= JSX::transform(data, JSX.transform_options)
13+
@output ||= JSX::transform(data)
1414
end
1515
end
1616
end

lib/react/jsx/transformer.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
module React
2+
module JSX
3+
class Transformer
4+
DEFAULT_ASSET_PATH = 'JSXTransformer.js'
5+
6+
def initialize(options)
7+
@transform_options = {
8+
stripTypes: options.fetch(:strip_types, false),
9+
harmony: options.fetch(:harmony, false),
10+
}
11+
12+
@asset_path = options.fetch(:asset_path, DEFAULT_ASSET_PATH)
13+
14+
# If execjs uses therubyracer, there is no 'global'. Make sure
15+
# we have it so JSX script can work properly.
16+
js_code = 'var global = global || this;' + jsx_transform_code
17+
@context = ExecJS.compile(js_code)
18+
end
19+
20+
21+
def transform(code)
22+
result = @context.call('JSXTransformer.transform', code, @transform_options)
23+
result["code"]
24+
end
25+
26+
def jsx_transform_code
27+
# search for transformer file using sprockets - allows user to override
28+
# this file in his own application
29+
::Rails.application.assets[@asset_path].to_s
30+
end
31+
end
32+
end
33+
end

test/jsxtransform_test.rb renamed to test/react/jsx_test.rb

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,26 @@
2020
}).call(this);
2121
eos
2222

23+
class NullTransformer
24+
def initialize(options={}); end
25+
def transform(code)
26+
"TRANSFORMED CODE!;\n"
27+
end
28+
end
29+
2330
class JSXTransformTest < ActionDispatch::IntegrationTest
31+
setup do
32+
clear_sprockets_cache
33+
end
34+
35+
teardown do
36+
clear_sprockets_cache
37+
React::JSX.transformer_class = React::JSX::Transformer
38+
React::JSX.transform_options = {}
39+
end
2440

2541
test 'asset pipeline should transform JSX' do
2642
get '/assets/example.js'
27-
FileUtils.rm_r CACHE_PATH if CACHE_PATH.exist?
2843
assert_response :success
2944
assert_equal EXPECTED_JS, @response.body
3045
end
@@ -40,14 +55,12 @@ class JSXTransformTest < ActionDispatch::IntegrationTest
4055
end
4156

4257
test 'can use dropped-in version of JSX transformer' do
43-
hidden_path = File.expand_path("../dummy/vendor/assets/react/JSXTransformer__.js", __FILE__)
44-
replacing_path = File.expand_path("../dummy/vendor/assets/react/JSXTransformer.js", __FILE__)
58+
hidden_path = Rails.root.join("vendor/assets/react/JSXTransformer__.js")
59+
replacing_path = Rails.root.join("vendor/assets/react/JSXTransformer.js")
4560

46-
FileUtils.mv hidden_path, replacing_path
61+
FileUtils.cp hidden_path, replacing_path
4762
get '/assets/example3.js'
48-
49-
FileUtils.mv replacing_path, hidden_path
50-
FileUtils.rm_r CACHE_PATH if CACHE_PATH.exist?
63+
FileUtils.rm replacing_path
5164

5265
assert_response :success
5366
assert_equal 'test_confirmation_token_jsx_transformed;', @response.body
@@ -69,4 +82,27 @@ class JSXTransformTest < ActionDispatch::IntegrationTest
6982
assert_response :success
7083
assert_match(/\(i\s*,\s*name\s*\)\s*\{/, @response.body, "type annotations are removed")
7184
end
85+
86+
test 'accepts asset_path: option' do
87+
hidden_path = Rails.root.join("vendor/assets/react/JSXTransformer__.js")
88+
custom_path = Rails.root.join("vendor/assets/react/custom")
89+
replacing_path = custom_path.join("CustomTransformer.js")
90+
91+
React::JSX.transform_options = {asset_path: "custom/CustomTransformer.js"}
92+
93+
FileUtils.mkdir_p(custom_path)
94+
FileUtils.cp(hidden_path, replacing_path)
95+
get '/assets/example3.js'
96+
97+
FileUtils.rm_rf custom_path
98+
assert_response :success
99+
assert_equal 'test_confirmation_token_jsx_transformed;', @response.body
100+
end
101+
102+
test 'use a custom transformer' do
103+
React::JSX.transformer_class = NullTransformer
104+
manually_expire_asset('example2.js')
105+
get '/assets/example2.js'
106+
assert_equal "TRANSFORMED CODE!;\n", @response.body
107+
end
72108
end

test/react_test.rb

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,23 @@
11
require 'test_helper'
22
class ReactTest < ActionDispatch::IntegrationTest
33
setup do
4-
FileUtils.rm_r(CACHE_PATH) if CACHE_PATH.exist?
5-
4+
clear_sprockets_cache
65
end
76

87
teardown do
9-
FileUtils.rm_r(CACHE_PATH) if CACHE_PATH.exist?
8+
clear_sprockets_cache
109
end
1110

1211
test 'asset pipeline should deliver drop-in react file replacement' do
1312
app_react_file_path = File.expand_path("../dummy/vendor/assets/javascripts/react.js", __FILE__)
1413
react_file_token = "'test_confirmation_token_react_content_non_production';\n"
1514
File.write(app_react_file_path, react_file_token)
16-
17-
react_asset = Rails.application.assets['react.js']
18-
19-
# Sprockets 2 doesn't expire this asset correctly,
20-
# so override `fresh?` to mark it as expired.
21-
def react_asset.fresh?(env); false; end
22-
15+
manually_expire_asset("react.js")
2316
react_asset = Rails.application.assets['react.js']
2417

2518
get '/assets/react.js'
2619

2720
File.unlink(app_react_file_path)
28-
FileUtils.rm_r(CACHE_PATH) if CACHE_PATH.exist?
2921

3022
assert_response :success
3123
assert_equal react_file_token.length, react_asset.to_s.length, "The asset pipeline serves the drop-in file"

test/test_helper.rb

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,20 @@
1616

1717
Rails.backtrace_cleaner.remove_silencers!
1818

19-
# Remove cached files
20-
Rails.root.join('tmp/cache').tap do |tmp|
21-
tmp.rmtree if tmp.exist?
22-
tmp.mkpath
19+
def clear_sprockets_cache
20+
# Remove cached files
21+
Rails.root.join('tmp/cache').tap do |tmp|
22+
tmp.rmtree if tmp.exist?
23+
tmp.mkpath
24+
end
25+
end
26+
27+
# Sprockets 2 doesn't expire this assets well in
28+
# this kind of setting,
29+
# so override `fresh?` to mark it as expired.
30+
def manually_expire_asset(asset_name)
31+
asset = Rails.application.assets[asset_name]
32+
def asset.fresh?(env); false; end
2333
end
2434

2535
# Load support files

0 commit comments

Comments
 (0)