Class: BridgetownLitRenderer::Renderer

Inherits:
Object
  • Object
show all
Includes:
Singleton
Defined in:
lib/bridgetown-lit-renderer/renderer.rb

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Class Attribute Details

.authtokenObject

Returns the value of attribute authtoken.



11
12
13
# File 'lib/bridgetown-lit-renderer/renderer.rb', line 11

def authtoken
  @authtoken
end

.serverpidObject

Returns the value of attribute serverpid.



11
12
13
# File 'lib/bridgetown-lit-renderer/renderer.rb', line 11

def serverpid
  @serverpid
end

.serverportObject

Returns the value of attribute serverport.



11
12
13
# File 'lib/bridgetown-lit-renderer/renderer.rb', line 11

def serverport
  @serverport
end

Instance Attribute Details

#siteObject

Returns the value of attribute site.



14
15
16
# File 'lib/bridgetown-lit-renderer/renderer.rb', line 14

def site
  @site
end

Class Method Details

.start_node_server(node_modules_path) ⇒ Object



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/bridgetown-lit-renderer/renderer.rb', line 16

def self.start_node_server(node_modules_path)
  return if serverpid

  self.authtoken =  SecureRandom.hex(64)
  self.serverport = RandomPort::Pool.new.acquire

  self.serverpid = spawn(
    {
      "LIT_SSR_SERVER_PORT" => serverport.to_s,
      "LIT_SSR_AUTH_TOKEN"  => authtoken,
      "NODE_PATH"           => node_modules_path,
    },
    "node #{File.expand_path("../../src/serve.js", __dir__)}",
    pgroup: true
  )
  Process.detach serverpid
  sleep 0.5
end

.stop_node_serverObject



35
36
37
38
39
40
41
42
# File 'lib/bridgetown-lit-renderer/renderer.rb', line 35

def self.stop_node_server
  return unless serverpid

  Process.kill("SIGTERM", -Process.getpgid(serverpid))
  self.serverpid = nil
  self.serverport = nil
rescue Errno::ESRCH, Errno::EPERM, Errno::ECHILD # rubocop:disable Lint/SuppressedException
end

Instance Method Details

#cacheObject



53
54
55
# File 'lib/bridgetown-lit-renderer/renderer.rb', line 53

def cache
  @cache ||= Bridgetown::Cache.new("LitSSR")
end

#call_http_server(payload) ⇒ Object



57
58
59
60
61
62
63
# File 'lib/bridgetown-lit-renderer/renderer.rb', line 57

def call_http_server(payload)
  Faraday.post(
    "http://127.0.0.1:#{self.class.serverport}",
    payload,
    "Authorization" => "Bearer #{self.class.authtoken}"
  ).body.force_encoding("utf-8")
end

#entry_key(entry) ⇒ Object



44
45
46
# File 'lib/bridgetown-lit-renderer/renderer.rb', line 44

def entry_key(entry)
  entry.start_with?("./") ? File.stat(site.in_root_dir(entry)).mtime : entry
end

#esbuild(code) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/bridgetown-lit-renderer/renderer.rb', line 65

def esbuild(code)
  raise "You must first assign the `site' accessor" unless site

  unless @esbuild_notice_printed
    Bridgetown.logger.info "Lit SSR:", "Bundling with esbuild..."
    @esbuild_notice_printed = true
  end

  # TODO: shouldn't this use the sidecar Node process as well?
  IO.popen(["node", site.in_root_dir("./config/lit-ssr.config.js")], "r+") do |pipe|
    pipe.puts({ code: code }.to_json)
    pipe.close_write
    pipe.read
  end
end

#js_code_block(entry, code) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/bridgetown-lit-renderer/renderer.rb', line 109

def js_code_block(entry, code)
  entry_import = "import #{entry.to_json}"
  <<~JS
    import { render } from "@lit-labs/ssr/lib/render-with-global-dom-shim.js"
    import { html } from "lit"
    #{entry_import}

    const ssrResult = render(html`
      #{code}
    `);

    let ret = []
    for (const chunk of ssrResult) {
      ret.push(chunk)
    }

    ret.join("")
  JS
end

#render(code, data:, entry:) ⇒ Object



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/bridgetown-lit-renderer/renderer.rb', line 81

def render(code, data:, entry:)
  raise "You must first assign the `site' accessor" unless site

  cache_key = "esbuild-#{code}#{entry}#{entry_key(entry)}"

  built_code = cache.getset(cache_key) { esbuild(js_code_block(entry, code)) }

  unless @render_notice_printed
    Bridgetown.logger.info "Lit SSR:", "Rendering components..."
    @render_notice_printed = true
  end

  self.class.start_node_server(site.in_root_dir("node_modules"))

  output = call_http_server("const data = #{data.to_json}; #{built_code}")

  if output == "SCRIPT NOT VALID!"
    output = <<~HTML
      <ssr-error style="display:block; padding:0.3em 0.5em; color:white; background:maroon; font-weight:bold">
        Lit SSR error in #{entry}, see logs
      </ssr-error>
    HTML
    cache.delete(cache_key)
  end

  output.html_safe
end

#resetObject



48
49
50
51
# File 'lib/bridgetown-lit-renderer/renderer.rb', line 48

def reset
  @esbuild_notice_printed = false
  @render_notice_printed = false
end