|
| 1 | +module React |
| 2 | + module ServerRendering |
| 3 | + class SprocketsRenderer |
| 4 | + def initialize(options={}) |
| 5 | + @replay_console = options.fetch(:replay_console, true) |
| 6 | + |
| 7 | + filenames = options.fetch(:files, ["react.js", "components.js"]) |
| 8 | + js_code = GLOBAL_WRAPPER + CONSOLE_POLYFILL |
| 9 | + |
| 10 | + filenames.each do |filename| |
| 11 | + js_code << ::Rails.application.assets[filename].to_s |
| 12 | + end |
| 13 | + |
| 14 | + @context = ExecJS.compile(js_code) |
| 15 | + end |
| 16 | + |
| 17 | + def render(component_name, props) |
| 18 | + # pass prerender: :static to use renderToStaticMarkup |
| 19 | + if props.is_a?(Hash) && props[:prerender] == :static |
| 20 | + react_render_method = "renderToStaticMarkup" |
| 21 | + else |
| 22 | + react_render_method = "renderToString" |
| 23 | + end |
| 24 | + |
| 25 | + if !props.is_a?(String) |
| 26 | + props = props.to_json |
| 27 | + end |
| 28 | + |
| 29 | + js_code = <<-JS |
| 30 | + (function () { |
| 31 | + var result = React.#{react_render_method}(React.createElement(#{component_name}, #{props})); |
| 32 | + #{@replay_console ? CONSOLE_REPLAY : ""} |
| 33 | + return result; |
| 34 | + })() |
| 35 | + JS |
| 36 | + |
| 37 | + @context.eval(js_code).html_safe |
| 38 | + rescue ExecJS::ProgramError => err |
| 39 | + raise PrerenderError.new(component_name, props, err) |
| 40 | + end |
| 41 | + |
| 42 | + # Handle node.js & other RubyRacer contexts |
| 43 | + GLOBAL_WRAPPER = <<-JS |
| 44 | + var global = global || this; |
| 45 | + var self = self || this; |
| 46 | + var window = window || this; |
| 47 | + JS |
| 48 | + |
| 49 | + # Reimplement console methods for replaying on the client |
| 50 | + CONSOLE_POLYFILL = <<-JS |
| 51 | + var console = { history: [] }; |
| 52 | + ['error', 'log', 'info', 'warn'].forEach(function (fn) { |
| 53 | + console[fn] = function () { |
| 54 | + console.history.push({level: fn, arguments: Array.prototype.slice.call(arguments)}); |
| 55 | + }; |
| 56 | + }); |
| 57 | + JS |
| 58 | + |
| 59 | + # Replay message from console history |
| 60 | + CONSOLE_REPLAY = <<-JS |
| 61 | + (function (history) { |
| 62 | + if (history && history.length > 0) { |
| 63 | + result += '\\n<scr'+'ipt>'; |
| 64 | + history.forEach(function (msg) { |
| 65 | + result += '\\nconsole.' + msg.level + '.apply(console, ' + JSON.stringify(msg.arguments) + ');'; |
| 66 | + }); |
| 67 | + result += '\\n</scr'+'ipt>'; |
| 68 | + } |
| 69 | + })(console.history); |
| 70 | + JS |
| 71 | + |
| 72 | + class PrerenderError < RuntimeError |
| 73 | + def initialize(component_name, props, js_message) |
| 74 | + message = ["Encountered error \"#{js_message}\" when prerendering #{component_name} with #{props}", |
| 75 | + js_message.backtrace.join("\n")].join("\n") |
| 76 | + super(message) |
| 77 | + end |
| 78 | + end |
| 79 | + end |
| 80 | + end |
| 81 | +end |
0 commit comments