module Isomorfeus module PreactViewHelper def self.included(base) base.include Isomorfeus::AssetManager::ViewHelper end def cached_mount_component(component_name, props = {}, asset_key = 'ssr.js', skip_ssr: false, use_ssr: false, max_passes: 4, refresh: false) key = "#{component_name}#{props}#{asset}" if !Isomorfeus.development? && !refresh render_result, @ssr_response_status, @ssr_styles = component_cache.fetch(key) return render_result if render_result end render_result = mount_component(component_name, props, asset_key, skip_ssr: skip_ssr, use_ssr: use_ssr, max_passes: max_passes) status = ssr_response_status component_cache.store(key, render_result, status, ssr_styles) if status >= 200 && status < 300 render_result end def mount_component(component_name, props = {}, asset_key = 'ssr.js', skip_ssr: false, use_ssr: false, max_passes: 4) ssr_start_time = Time.now if Isomorfeus.development? @ssr_response_status = nil @ssr_styles = nil render_result = "
e Isomorfeus.raise_error(message: "Server Side Rendering: Failed creating context for #{asset_key}. Error: #{e.message}", stack: e.backtrace) end ctx = Isomorfeus.ssr_contexts[thread_id_asset] pass = 0 # if location_host and scheme are given and if Transport is loaded, connect and then render ws_scheme = props[:location_scheme] == 'https:' ? 'wss:' : 'ws:' location_host = props[:location_host] ? props[:location_host] : 'localhost' api_ws_path = Isomorfeus.respond_to?(:api_websocket_path) ? Isomorfeus.api_websocket_path : '' transport_ws_url = ws_scheme + location_host + api_ws_path # build javascript for rendering first pass # it will initialize buffers to guard against leaks, maybe caused by previous exceptions javascript = <<~JAVASCRIPT return Opal.Isomorfeus.SSR.mount_component('#{Thread.current[:isomorfeus_session_id]}', '#{Isomorfeus.env}', '#{props[:locale]}', '#{props[:location]}', '#{transport_ws_url}', '#{component_name}', #{Oj.dump(props, mode: :strict)}, #{max_passes}) JAVASCRIPT begin ctx.exec(javascript) rescue Exception => e Isomorfeus.raise_error(error: e) end sleep 0.001 # give other threads that would respond to a request from node/SSR a chance to run rendering = ctx.eval_script(key: :rendering) if rendering start_time = Time.now while rendering break if (Time.now - start_time) > 10 sleep 0.005 rendering = ctx.eval_script(key: :rendering) end end rendered_tree, application_state, @ssr_styles, @ssr_response_status, passes, exception = ctx.eval_script(key: :get_result) Isomorfeus.raise_error(message: "Server Side Rendering: #{exception['message']}", stack: exception['stack']) if exception render_result << " data-iso-hydrated='true'" if rendered_tree if Isomorfeus.respond_to?(:current_user) && Isomorfeus.current_user && !Isomorfeus.current_user.anonymous? render_result << " data-iso-usid=#{Oj.dump(Isomorfeus.current_user.sid, mode: :strict)}" end render_result << " data-iso-nloc='#{props[:locale]}'>" render_result << (rendered_tree ? rendered_tree : "SSR didn't work") else if Isomorfeus.respond_to?(:current_user) && Isomorfeus.current_user && !Isomorfeus.current_user.anonymous? render_result << " data-iso-usid=#{Oj.dump(Isomorfeus.current_user.sid, mode: :strict)}" end render_result << " data-iso-nloc='#{props[:locale]}'>" end render_result << '
' if Isomorfeus.server_side_rendering && !skip_ssr render_result = "\n" << render_result puts "PreactViewHelper Server Side Rendering rendered #{passes} passes and took ~#{((Time.now - ssr_start_time)*1000).to_i}ms" if Isomorfeus.development? end render_result end def ssr_response_status @ssr_response_status || 200 end def ssr_styles @ssr_styles || '' end private def asset_manager @_asset_manager ||= Isomorfeus::AssetManager.new end def component_cache @_component_cache ||= Isomorfeus.component_cache_init_block.call end def init_speednode_context(asset_key, thread_id_asset) asset = Isomorfeus.assets[asset_key] raise "#{self.class.name}: Asset not found: #{asset_key}" unless asset if !Isomorfeus.ssr_contexts.key?(thread_id_asset) || !asset.bundled? if Isomorfeus.ssr_contexts.key?(thread_id_asset) uuid = Isomorfeus.ssr_contexts[thread_id_asset].instance_variable_get(:@uuid) runtime = Isomorfeus.ssr_contexts[thread_id_asset].instance_variable_get(:@runtime) runtime.vm.delete_context(uuid) end asset_manager.transition(asset_key, asset) Isomorfeus.ssr_contexts[thread_id_asset] = ExecJS.permissive_compile(asset.bundle) ctx = Isomorfeus.ssr_contexts[thread_id_asset] ctx.add_script(key: :get_result, source: 'Opal.Isomorfeus.SSR.get_result()') ctx.add_script(key: :rendering, source: 'global.Rendering') end end def ssr_mod @_ssr_mod ||= Opal.compile(File.read(File.expand_path(File.join(File.dirname(__FILE__), 'ssr.rb'))), { use_strict: true }) end def top_level_mod @_top_level_mod ||= Opal.compile(File.read(File.expand_path(File.join(File.dirname(__FILE__), 'top_level_ssr.rb'))), { use_strict: true }) end end end