reactrb 0.8.7 → 0.8.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ddbe495a34c70871decbabe9d4caf2816922686c
4
- data.tar.gz: c04dd8a078c54622c08dc2442d593d21cea54fca
3
+ metadata.gz: 56bb10a0b7e0570fcd71cd6eb8a84b683d7c5477
4
+ data.tar.gz: 6f3556f79685a0ed59fa24e3adffe9865f2c37d7
5
5
  SHA512:
6
- metadata.gz: d1465573197ce0818e16208eab0dd5155e6bb121ca1219df7ea9a12b2a44181e14c6b2fcb0076455ce9fd70441d4112b23d340afe12dc9d3c4c1f62d09caa28b
7
- data.tar.gz: 87b26b83e9467871b9d3be9a7e905060e5ddbb4893b1ae10e63ae56f36bb74d512af5ded00b5392f9a77742398998617e94a8252dc263c924dd51c1a8b8ff319
6
+ metadata.gz: 659a94fbc934575d85084a11a0006ccb358abe68cf9a77553bf2d64e794bd5eb424ff1399dfaac62cb37d7b4c5c7ec5c00b225eae49939b01af24262de989b42
7
+ data.tar.gz: e42a8bf45da2475c6069b10956a770bdbffb73ee53cb96f72f1021a7e7e832b9cab9fc381e3ba11f29e08fbbccf3e513fa0ffbb1258cfa4c9ad6ef97024cc9d5
data/CHANGELOG.md CHANGED
@@ -6,7 +6,7 @@ This project *tries* to adhere to [Semantic Versioning](http://semver.org/), eve
6
6
  Changes are grouped as follows:
7
7
  - **Added** for new features.
8
8
  - **Changed** for changes in existing functionality.
9
- - **Deprecated** for once-stable features removed in upcoming releases.
9
+ - **Deprecated** for once-stable features to be removed in upcoming releases.
10
10
  - **Removed** for deprecated features removed in this release.
11
11
  - **Fixed** for any bug fixes.
12
12
  - **Security** to invite users to upgrade in case of vulnerabilities.
@@ -18,7 +18,35 @@ Whitespace conventions:
18
18
  - 1 spaces before normal text
19
19
  -->
20
20
 
21
+ ## [0.8.8] - 2016-07-13
21
22
 
23
+ ### Added
24
+
25
+ - More helpful error messages on render failures (#152)
26
+ - `Element#on('<my_event_name>')` subscribes `my_event_name` (#153)
27
+
28
+ ### Changed
29
+
30
+ - `Element#on(:event)` subscribes to `on_event` for reactrb components and `onEvent` for native components. (#153)
31
+
32
+ ### Deprecated
33
+
34
+ - `Element#(:event)` subscription to `_onEvent` is deprecated. Once you have changed params named `_on...` to `on_...` you can `require 'reactrb/new-event-name-convention.rb'` to avoid spurious react warning messages. (#153)
35
+
36
+
37
+ ### Fixed
38
+
39
+ - when using the Element['#container'].render... method generates spurious react error (#154)
40
+
41
+
42
+
43
+
44
+ ## [0.8.7] - 2016-07-08
45
+
46
+
47
+ ### Fixed
48
+
49
+ - Opal 0.10.x compatibility
22
50
 
23
51
 
24
52
  ## [0.8.6] - 2016-06-30
data/README.md CHANGED
@@ -21,7 +21,7 @@ Install the gem, or load the js library
21
21
 
22
22
  + add `gem 'reactrb'` to your gem file or
23
23
  + `gem install reactrb` or
24
- + install (or load via cdn) [inline-reactrb.js](http://github.com/reactrb/inline-reactrb)
24
+ + install (or load via cdn) [reactrb-express.js](http://github.com/reactrb/reactrb-express)
25
25
 
26
26
  For gem installation it is highly recommended to read [the getting started section at reactrb.org](http://reactrb.org/docs/getting-started.html)
27
27
 
data/lib/react/api.rb CHANGED
@@ -16,6 +16,7 @@ module React
16
16
  @@component_classes = {}
17
17
 
18
18
  def self.import_native_component(opal_class, native_class)
19
+ opal_class.instance_variable_set("@native_import", true)
19
20
  @@component_classes[opal_class] = native_class
20
21
  end
21
22
 
@@ -2,6 +2,11 @@ module React
2
2
  module Component
3
3
  # class level methods (macros) for components
4
4
  module ClassMethods
5
+
6
+ def reactrb_component?
7
+ true
8
+ end
9
+
5
10
  def backtrace(*args)
6
11
  @dont_catch_exceptions = (args[0] == :none)
7
12
  @backtrace_off = @dont_catch_exceptions || (args[0] == :off)
@@ -43,7 +48,7 @@ module React
43
48
  _componentValidator: %x{
44
49
  function(props, propName, componentName) {
45
50
  var errors = #{validator.validate(Hash.new(`props`))};
46
- var error = new Error(#{"In component `" + self.name + "`\n" + `errors`.join("\n")});
51
+ var error = new Error(#{"In component `#{name}`\n" + `errors`.join("\n")});
47
52
  return #{`errors`.count > 0 ? `error` : `undefined`};
48
53
  }
49
54
  }
data/lib/react/element.rb CHANGED
@@ -1,6 +1,18 @@
1
1
  require 'react/ext/string'
2
2
 
3
3
  module React
4
+ #
5
+ # Wraps the React Native element class
6
+ #
7
+ # adds the #on method to add event handlers to the element
8
+ #
9
+ # adds the #render method to place elements in the DOM and
10
+ # #delete (alias/deprecated #as_node) method to remove elements from the DOM
11
+ #
12
+ # handles the haml style class notation so that
13
+ # div.bar.blat becomes div(class: "bar blat")
14
+ # by using method missing
15
+ #
4
16
  class Element
5
17
  include Native
6
18
 
@@ -20,58 +32,124 @@ module React
20
32
  @native = native_element
21
33
  end
22
34
 
23
- def on(event_name)
24
- name = event_name.to_s.event_camelize
25
- props = if React::Event::BUILT_IN_EVENTS.include?("on#{name}")
26
- {"on#{name}" => %x{
27
- function(event){
28
- #{yield React::Event.new(`event`)}
29
- }
30
- }}
31
- else
32
- {"_on#{name}" => %x{
33
- function(){
34
- #{yield *Array(`arguments`)}
35
- }
36
- }}
37
- end
38
- @native = `React.cloneElement(#{self.to_n}, #{props.to_n})`
39
- @properties.merge! props
35
+ # Attach event handlers.
36
+
37
+ def on(*event_names, &block)
38
+ event_names.each { |event_name| merge_event_prop!(event_name, &block) }
39
+ @native = `React.cloneElement(#{to_n}, #{properties.to_n})`
40
40
  self
41
41
  end
42
42
 
43
- def render(props = {}) # for rendering children
43
+ # Render element into DOM in the current rendering context.
44
+ # Used for elements that are not yet in DOM, i.e. they are provided as children
45
+ # or they have been explicitly removed from the rendering context using the delete method.
46
+
47
+ def render(props = {})
44
48
  if props.empty?
45
49
  React::RenderingContext.render(self)
46
50
  else
47
51
  React::RenderingContext.render(
48
52
  Element.new(
49
- `React.cloneElement(#{self.to_n}, #{API.convert_props(props)})`,
50
- type,
51
- properties.merge(props),
52
- block
53
+ `React.cloneElement(#{to_n}, #{API.convert_props(props)})`,
54
+ type, properties.merge(props), block
53
55
  )
54
56
  )
55
57
  end
56
58
  end
57
59
 
60
+ # Delete (remove) element from rendering context, the element may later be added back in
61
+ # using the render method.
62
+
63
+ def delete
64
+ React::RenderingContext.delete(self)
65
+ end
66
+
67
+ # Deprecated version of delete method
68
+
69
+ def as_node
70
+ React::RenderingContext.as_node(self)
71
+ end
72
+
73
+ # Any other method applied to an element will be treated as class name (haml style) thus
74
+ # div.foo.bar(id: :fred) is the same as saying div(class: "foo bar", id: :fred)
75
+ #
76
+ # single underscores become dashes, and double underscores become a single underscore
77
+ #
78
+ # params may be provide to each class (but typically only to the last for easy reading.)
79
+
58
80
  def method_missing(class_name, args = {}, &new_block)
59
- class_name = class_name.split("__").collect { |s| s.gsub("_", "-") }.join("_")
81
+ class_name = class_name.gsub(/__|_/, '__' => '_', '_' => '-')
60
82
  new_props = properties.dup
61
- new_props["class"] = "#{new_props['class']} #{class_name} #{args.delete("class")} #{args.delete('className')}".split(" ").uniq.join(" ")
83
+ new_props[:class] = "\
84
+ #{class_name} #{new_props[:class]} #{args.delete(:class)} #{args.delete(:className)}\
85
+ ".split(' ').uniq.join(' ')
62
86
  new_props.merge! args
63
87
  React::RenderingContext.replace(
64
88
  self,
65
- React::RenderingContext.build { React::RenderingContext.render(type, new_props, &new_block) }
89
+ RenderingContext.build { RenderingContext.render(type, new_props, &new_block) }
66
90
  )
67
91
  end
68
92
 
69
- def as_node
70
- React::RenderingContext.as_node(self)
93
+ private
94
+
95
+ # built in events, events going to native components, and events going to reactrb
96
+
97
+ # built in events will have their event param translated to the Event wrapper
98
+ # and the name will camelcased and have on prefixed, so :click becomes onClick.
99
+ #
100
+ # events emitting from native components are assumed to have the same camel case and
101
+ # on prefixed.
102
+ #
103
+ # events emitting from reactrb components will just have on_ prefixed. So
104
+ # :play_button_pushed attaches to the :on_play_button_pushed param
105
+ #
106
+ # in all cases the default name convention can be overriden by wrapping in <...> brackets.
107
+ # So on("<MyEvent>") will attach to the "MyEvent" param.
108
+
109
+ def merge_event_prop!(event_name, &block)
110
+ if event_name =~ /^<(.+)>$/
111
+ merge_component_event_prop! event_name.gsub(/^<(.+)>$/, '\1'), &block
112
+ elsif React::Event::BUILT_IN_EVENTS.include?(name = "on#{event_name.event_camelize}")
113
+ merge_built_in_event_prop! name, &block
114
+ elsif @type.instance_variable_get('@native_import')
115
+ merge_component_event_prop! name, &block
116
+ else
117
+ merge_deprecated_component_event_prop! event_name, &block
118
+ merge_component_event_prop! "on_#{event_name}", &block
119
+ end
71
120
  end
72
121
 
73
- def delete
74
- React::RenderingContext.delete(self)
122
+ def merge_built_in_event_prop!(prop_name)
123
+ @properties.merge!(
124
+ prop_name => %x{
125
+ function(event){
126
+ return #{yield(React::Event.new(`event`))}
127
+ }
128
+ }
129
+ )
130
+ end
131
+
132
+ def merge_component_event_prop!(prop_name)
133
+ @properties.merge!(
134
+ prop_name => %x{
135
+ function(){
136
+ return #{yield(*Array(`arguments`))}
137
+ }
138
+ }
139
+ )
140
+ end
141
+
142
+ def merge_deprecated_component_event_prop!(event_name)
143
+ prop_name = "_on#{event_name.event_camelize}"
144
+ fn = %x{function(){#{
145
+ React::Component.deprecation_warning(
146
+ "In future releases React::Element#on('#{event_name}') will no longer respond "\
147
+ "to the '#{prop_name}' emitter.\n"\
148
+ "Rename your emitter param to 'on_#{event_name}' or use .on('<#{prop_name}>')"
149
+ )}
150
+ return #{yield(*Array(`arguments`))}
151
+ }}
152
+ @properties.merge!(prop_name => fn)
75
153
  end
76
154
  end
77
155
  end
@@ -13,24 +13,12 @@ module React
13
13
 
14
14
  def self.render(name, *args, &block)
15
15
  remove_nodes_from_args(args)
16
- @buffer = [] unless @buffer
16
+ @buffer ||= [] unless @buffer
17
17
  if block
18
18
  element = build do
19
19
  saved_waiting_on_resources = waiting_on_resources
20
20
  self.waiting_on_resources = nil
21
- result = block.call
22
- # Todo figure out how children rendering should happen, probably should have special method that pushes children into the buffer
23
- # i.e. render_child/render_children that takes Element/Array[Element] and does the push into the buffer
24
- if !name && ( # !name means called from outer render so we check that it has rendered correctly
25
- (@buffer.count > 1) || # should only render one element
26
- (@buffer.count == 1 && @buffer.last != result) || # it should return that element
27
- (@buffer.count == 0 && !(result.is_a?(String) || (result.respond_to?(:acts_as_string?) && result.acts_as_string?) || result.is_a?(Element))) #for convience we will also convert the return value to a span if its a string
28
- )
29
- raise "a components render method must generate and return exactly 1 element or a string"
30
- end
31
-
32
- @buffer << result.to_s if result.is_a? String || (result.respond_to?(:acts_as_string?) && result.acts_as_string?) # For convience we push the last return value on if its a string
33
- @buffer << result if result.is_a?(Element) && @buffer.count == 0
21
+ run_child_block(name.nil?, &block)
34
22
  if name
35
23
  buffer = @buffer.dup
36
24
  React.create_element(name, *args) { buffer }.tap do |element|
@@ -44,7 +32,6 @@ module React
44
32
  end
45
33
  elsif name.is_a? React::Element
46
34
  element = name
47
- # I BELIEVE WAITING ON RESOURCES SHOULD ALREADY BE SET
48
35
  else
49
36
  element = React.create_element(name, *args)
50
37
  element.waiting_on_resources = waiting_on_resources
@@ -78,6 +65,51 @@ module React
78
65
  value.as_node if value.is_a?(Element) rescue nil
79
66
  end if args[0] && args[0].is_a?(Hash)
80
67
  end
68
+
69
+ # run_child_block gathers the element(s) generated by a child block.
70
+ # for example when rendering this div: div { "hello".span; "goodby".span }
71
+ # two child Elements will be generated.
72
+ #
73
+ # the final value of the block should either be
74
+ # 1 an object that responds to :acts_as_string?
75
+ # 2 a string,
76
+ # 3 an element that is NOT yet pushed on the rendering buffer
77
+ # 4 or the last element pushed on the buffer
78
+ #
79
+ # in case 1 we change the object to a string, and then it becomes case 2
80
+ # in case 2 we automatically push the string onto the buffer
81
+ # in case 3 we also push the Element onto the buffer IF the buffer is empty
82
+ # case 4 requires no special processing
83
+ #
84
+ # Once we have taken care of these special cases we do a check IF we are in an
85
+ # outer rendering scope. In this case react only allows us to generate 1 Element
86
+ # so we insure that is the case, and also check to make sure that element in the buffer
87
+ # is the element returned
88
+
89
+ def self.run_child_block(is_outer_scope)
90
+ result = yield
91
+ result = result.to_s if result.try :acts_as_string?
92
+ @buffer << result if result.is_a?(String) || (result.is_a?(Element) && @buffer.empty?)
93
+ raise_render_error(result) if is_outer_scope && @buffer != [result]
94
+ end
95
+
96
+ # heurestically raise a meaningful error based on the situation
97
+
98
+ def self.raise_render_error(result)
99
+ improper_render 'A different element was returned than was generated within the DSL.',
100
+ 'Possibly improper use of Element#delete.' if @buffer.count == 1
101
+ improper_render "Instead #{@buffer.count} elements were generated.",
102
+ 'Do you want to wrap your elements in a div?' if @buffer.count > 1
103
+ improper_render "Instead the component #{result} was returned.",
104
+ "Did you mean #{result}()?" if result.try :reactrb_component?
105
+ improper_render "Instead the #{result.class} #{result} was returned.",
106
+ 'You may need to convert this to a string.'
107
+ end
108
+
109
+ def self.improper_render(message, solution)
110
+ raise "a component's render method must generate and return exactly 1 element or a string.\n"\
111
+ " #{message} #{solution}"
112
+ end
81
113
  end
82
114
 
83
115
  class ::Object
@@ -1,3 +1,3 @@
1
1
  module React
2
- VERSION = '0.8.7'
2
+ VERSION = '0.8.8'
3
3
  end
@@ -0,0 +1,11 @@
1
+ # rubocop:disable Style/FileName
2
+ # require 'reactrb/new-event-name-convention' to remove missing param declaration "_onXXXX"
3
+ if RUBY_ENGINE == 'opal'
4
+ # removes generation of the deprecated "_onXXXX" event param syntax
5
+ module React
6
+ class Element
7
+ def merge_deprecated_component_event_prop!(event_name)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -477,6 +477,77 @@ describe React::Component do
477
477
  end
478
478
  end
479
479
 
480
+ describe 'Anonymous Component' do
481
+ it "will not generate spurious warning messages" do
482
+ foo = Class.new(React::Component::Base)
483
+ foo.class_eval do
484
+ def render; "hello" end
485
+ end
486
+
487
+ %x{
488
+ var log = [];
489
+ var org_warn_console = window.console.warn;
490
+ var org_error_console = window.console.error
491
+ window.console.warn = window.console.error = function(str){log.push(str)}
492
+ }
493
+ renderToDocument(foo)
494
+ `window.console.warn = org_warn_console; window.console.error = org_error_console;`
495
+ expect(`log`).to eq([])
496
+ end
497
+ end
498
+
499
+ describe 'Render Error Handling' do
500
+ before(:each) do
501
+ %x{
502
+ window.test_log = [];
503
+ window.org_warn_console = window.console.warn;
504
+ window.org_error_console = window.console.error
505
+ window.console.warn = window.console.error = function(str){window.test_log.push(str)}
506
+ }
507
+ end
508
+ it "will generate a message if render returns something other than an Element or a String" do
509
+ foo = Class.new(React::Component::Base)
510
+ foo.class_eval do
511
+ def render; Hash.new; end
512
+ end
513
+
514
+ renderToDocument(foo)
515
+ `window.console.warn = window.org_warn_console; window.console.error = window.org_error_console;`
516
+ expect(`test_log`.first).to match /Instead the Hash \{\} was returned/
517
+ end
518
+ it "will generate a message if render returns a Component class" do
519
+ stub_const 'Foo', Class.new(React::Component::Base)
520
+ foo = Class.new(React::Component::Base)
521
+ foo.class_eval do
522
+ def render; Foo; end
523
+ end
524
+
525
+ renderToDocument(foo)
526
+ `window.console.warn = window.org_warn_console; window.console.error = window.org_error_console;`
527
+ expect(`test_log`.first).to match /Did you mean Foo()/
528
+ end
529
+ it "will generate a message if more than 1 element is generated" do
530
+ foo = Class.new(React::Component::Base)
531
+ foo.class_eval do
532
+ def render; "hello".span; "goodby".span; end
533
+ end
534
+
535
+ renderToDocument(foo)
536
+ `window.console.warn = window.org_warn_console; window.console.error = window.org_error_console;`
537
+ expect(`test_log`.first).to match /Instead 2 elements were generated/
538
+ end
539
+ it "will generate a message if the element generated is not the element returned" do
540
+ foo = Class.new(React::Component::Base)
541
+ foo.class_eval do
542
+ def render; "hello".span; "goodby".span.delete; end
543
+ end
544
+
545
+ renderToDocument(foo)
546
+ `window.console.warn = window.org_warn_console; window.console.error = window.org_error_console;`
547
+ expect(`test_log`.first).to match /A different element was returned than was generated within the DSL/
548
+ end
549
+ end
550
+
480
551
  describe 'Event handling' do
481
552
  before do
482
553
  stub_const 'Foo', Class.new
@@ -1,6 +1,8 @@
1
1
  require "spec_helper"
2
2
 
3
3
  if opal?
4
+ # require 'reactrb/new-event-name-convention' # this require will get rid of any error messages but
5
+ # the on method will no longer attach to the param prefixed with _on
4
6
  describe React::Element do
5
7
  it 'bridges `type` of native React.Element attributes' do
6
8
  element = React.create_element('div')
@@ -17,7 +19,79 @@ describe React::Element do
17
19
  end
18
20
  end
19
21
 
20
- describe 'Event subscription' do
22
+ describe 'Component Event Subscription' do
23
+
24
+ it 'will subscribe to a component event param' do
25
+ stub_const 'Foo', Class.new(React::Component::Base)
26
+ Foo.class_eval do
27
+ param :on_event, type: Proc, default: nil, allow_nil: true
28
+ def render
29
+ params.on_event
30
+ end
31
+ end
32
+ expect(React.render_to_static_markup(React.create_element(Foo).on(:event) {'works!'})).to eq('<span>works!</span>')
33
+ end
34
+
35
+ it 'will subscribe to multiple component event params' do
36
+ stub_const 'Foo', Class.new(React::Component::Base)
37
+ Foo.class_eval do
38
+ param :on_event1, type: Proc, default: nil, allow_nil: true
39
+ param :on_event2, type: Proc, default: nil, allow_nil: true
40
+ def render
41
+ params.on_event1+params.on_event2
42
+ end
43
+ end
44
+ expect(React.render_to_static_markup(React.create_element(Foo).on(:event1, :event2) {'works!'})).to eq('<span>works!works!</span>')
45
+ end
46
+
47
+ it 'will subscribe to a native components event param' do
48
+ %x{
49
+ window.NativeComponent = React.createClass({
50
+ displayName: "HelloMessage",
51
+ render: function render() {
52
+ return React.createElement("span", null, this.props.onEvent());
53
+ }
54
+ })
55
+ }
56
+ stub_const 'Foo', Class.new(React::Component::Base)
57
+ Foo.class_eval do
58
+ imports "NativeComponent"
59
+ end
60
+ expect(React.render_to_static_markup(React.create_element(Foo).on(:event) {'works!'})).to eq('<span>works!</span>')
61
+ end
62
+
63
+ it 'will subscribe to a component event param with a non-default name' do
64
+ stub_const 'Foo', Class.new(React::Component::Base)
65
+ Foo.class_eval do
66
+ param :my_event, type: Proc, default: nil, allow_nil: true
67
+ def render
68
+ params.my_event
69
+ end
70
+ end
71
+ expect(React.render_to_static_markup(React.create_element(Foo).on("<my_event>") {'works!'})).to eq('<span>works!</span>')
72
+ end
73
+
74
+ it 'will subscribe to a component event param using the deprecated naming convention and generate a message' do
75
+ stub_const 'Foo', Class.new(React::Component::Base)
76
+ Foo.class_eval do
77
+ param :_onEvent, type: Proc, default: nil, allow_nil: true
78
+ def render
79
+ params._onEvent
80
+ end
81
+ end
82
+ %x{
83
+ var log = [];
84
+ var org_warn_console = window.console.warn;
85
+ var org_error_console = window.console.error;
86
+ window.console.warn = window.console.error = function(str){log.push(str)}
87
+ }
88
+ expect(React.render_to_static_markup(React.create_element(Foo).on(:event) {'works!'})).to eq('<span>works!</span>')
89
+ `window.console.warn = org_warn_console; window.console.error = org_error_console;`
90
+ expect(`log`).to eq(["Warning: Failed propType: In component `Foo`\nProvided prop `on_event` not specified in spec", "Warning: Deprecated feature used in React::Component. In future releases React::Element#on('event') will no longer respond to the '_onEvent' emitter.\nRename your emitter param to 'on_event' or use .on('<_onEvent>')"])
91
+ end
92
+ end
93
+
94
+ describe 'Builtin Event subscription' do
21
95
  it 'is subscribable through `on(:event_name)` method' do
22
96
  expect { |b|
23
97
  element = React.create_element("div").on(:click, &b)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: reactrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.7
4
+ version: 0.8.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Chang
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-07-08 00:00:00.000000000 Z
13
+ date: 2016-07-13 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: opal
@@ -377,6 +377,7 @@ files:
377
377
  - lib/reactive-ruby/version.rb
378
378
  - lib/reactrb.rb
379
379
  - lib/reactrb/auto-import.rb
380
+ - lib/reactrb/new-event-name-convention.rb
380
381
  - lib/sources/react-latest.js
381
382
  - lib/sources/react-v13.js
382
383
  - lib/sources/react-v14.js