humid 0.0.1 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 331e268f4bfc9f9b46e4246a9fb1af56245616bedecfb13a9b220040fca74636
4
- data.tar.gz: 87631778cddbd5511202a0a3aaaa54fb6827b8aee8bdae29f80297ee6764cd55
3
+ metadata.gz: a783b726d5542a0abfc94fc1918aad0212c13ac7b6db8b8efd90c5452a222b2e
4
+ data.tar.gz: b9c3719ace7b00d210c992b48f9c4ee02010e81d19c30aa47e8bf6fdd9e9373b
5
5
  SHA512:
6
- metadata.gz: 5161834e3f6b0a8d6cb9e991965df392491e204373c0ec54773cf114f8c489be88f435a5d6ba90c226dc6a698ea053ca3ccaf64581cb01399272057ecb600990
7
- data.tar.gz: a545a0ffb5c7f1dfdf333bb960566a28fb355fd3dfc60ede7ed88f3210f507ca21e7b389138c07b2a773903110df66b7e60df844a57815c0a87fb624a15ee44c
6
+ metadata.gz: d29027f962022c6f8d5aeaf09c11730d572a32f8936e025fc04c3490921ac51468f6567e99f29aeb090a344150fa5edce2bbe14e83c18bfe9afc62e89fb34a87
7
+ data.tar.gz: 9ceb09c4bebef88f71f3b9c229c9317526a1d5cd5100dce4a270aad383334401a14baa7e108417170c5c3cad122523e9539733404d4c9a3cdf6f7bb8c2ec5dbf
data/README.md CHANGED
@@ -1,11 +1,12 @@
1
1
  # Humid
2
-
3
- Humid is a lightweight helper that leans on [mini_racer] and [webpacker] to
4
- generate Server Side Rendered (SSR) pages from your javascript application.
5
-
6
2
  [![Build
7
3
  Status](https://circleci.com/gh/thoughtbot/humid.svg?style=shield)](https://circleci.com/gh/thoughtbot/humid)
8
4
 
5
+ Humid is a lightweight wrapper around [mini_racer] and [webpacker] used to
6
+ generate Server Side Rendered (SSR) pages from your javascript application.
7
+ While it was built for React, it can work with any JS function that returns a
8
+ HTML string.
9
+
9
10
  ## Caution
10
11
 
11
12
  This project is in its early phases of development. Its interface,
@@ -33,30 +34,51 @@ Add an initializer to configure
33
34
 
34
35
  ```ruby
35
36
  Humid.configure do |config|
36
- # name of your webpacker pack. Defaults to "server_rendering.js"
37
- config.server_rendering_source = "server_rendering.js"
38
-
39
- # name of your webpacker pack source map. Defaults to `nil`
40
- config.server_rendering_source_map = "server_rendering.js.map"
41
-
42
- # The logger instance. Defaults to `Logger.new(STDOUT)`
37
+ # Name of your webpacker pack file located in `app/javascript/packs/`. You
38
+ # should use a separate pack from your `application.js`.
39
+ #
40
+ # Defaults to "server_rendering.js"
41
+ config.server_rendering_pack = "server_rendering.js"
42
+
43
+ # Name of your webpacker pack source map.
44
+ #
45
+ # Defaults to `false`
46
+ config.use_source_map = true
47
+
48
+ # Raise errors if JS rendering failed. If false, the error will be
49
+ # logged out to Rails log and Humid.render will return an empty string
50
+ #
51
+ # Defaults to true.
52
+ config.raise_render_errors = Rails.env.development? || Rails.env.test?
53
+
54
+ # The logger instance.
43
55
  # `console.log` and friends (`warn`, `error`) are delegated to
44
56
  # the respective logger levels on the ruby side.
57
+ #
58
+ # Defaults to `Logger.new(STDOUT)`
45
59
  config.logger = Rails.logger
46
60
 
47
- # context_options. Options passed to mini_racer. Defaults to
48
- # config.context_options = {
49
- # timeout: 1000,
50
- # ensure_gc_after_idle: 2000
51
- # }
61
+ # Options passed to mini_racer.
62
+ #
63
+ # Defaults to empty `{}`.
64
+ config.context_options = {
65
+ timeout: 1000,
66
+ ensure_gc_after_idle: 2000
67
+ }
52
68
  end
53
69
 
54
- # If using puma in single mode
55
- # Humid.create_context
70
+ # Common development options
71
+ if Rails.env.development? || Rails.env.test?
72
+ # Use single_threaded mode for Spring and other forked envs.
73
+ MiniRacer::Platform.set_flags! :single_threaded
74
+
75
+ # If you're using Puma in single mode:
76
+ Humid.create_context
77
+ end
56
78
  ```
57
79
 
58
80
  If you'd like support for source map support, you will need to
59
- 1. Ensure `config.server_rendering_source_map` has a value
81
+ 1. Ensure `config.use_source_map` is set to `true`
60
82
  2. Add the following to your `server_rendering.js` pack.
61
83
 
62
84
  ```javascript
@@ -72,6 +94,8 @@ require("source-map-support").install({
72
94
 
73
95
  ## The mini_racer environment.
74
96
 
97
+ ### Functions not available
98
+
75
99
  The following functions are **not** available in the mini_racer environment
76
100
 
77
101
  - `setTimeout`
@@ -86,11 +110,59 @@ The following functions are **not** available in the mini_racer environment
86
110
  `console.log` and friends (`info`, `error`, `warn`) are delegated to the
87
111
  respective methods on the configured logger.
88
112
 
89
- ### Webpacker
113
+ ## Usage
114
+
115
+ Pass your HTML render function to `setHumidRenderer`
116
+
117
+ ```javascript
118
+ // Set a factory function that will create a new instance of our app
119
+ // for each request.
120
+ setHumidRenderer((json) => {
121
+ const initialState = JSON.parse(json)
122
+
123
+ return ReactDOMServer.renderToString(
124
+ <Application initialPage={initialState}/>
125
+ )
126
+ })
127
+ ```
128
+
129
+ And finally call `render` from ERB.
130
+
131
+ ```ruby
132
+ <%= Humid.render(initial_state) %>
133
+ ```
134
+
135
+ Instrumentation is included:
136
+
137
+ ```
138
+ Completed 200 OK in 14ms (Views: 0.2ms | Humid SSR: 11.0ms | ActiveRecord: 2.7ms)
139
+ ```
140
+
141
+ ### Puma
142
+
143
+ `mini_racer` is thread safe, but not fork safe. To use with web servers that
144
+ employ forking, use `Humid.create_context` only on forked processes.
145
+
146
+ ```ruby
147
+ # Puma
148
+ on_worker_boot do
149
+ Humid.create_context
150
+ end
151
+
152
+ on_worker_shutdown do
153
+ Humid.dispose
154
+ end
155
+ ```
156
+
157
+ ### Server-side libraries that detect node.js envs.
90
158
  You may need webpacker to create aliases for server friendly libraries that can
91
159
  not detect the `mini_racer` environment.
92
160
 
93
161
  ```diff
162
+ // config/webpack/production.js
163
+ // config/webpack/development.js
164
+ // config/webpack/test.js
165
+
94
166
  process.env.NODE_ENV = process.env.NODE_ENV || 'development'
95
167
 
96
168
  const environment = require('./environment')
@@ -117,39 +189,32 @@ not detect the `mini_racer` environment.
117
189
  +module.exports = [ssrConfig, webConfig]
118
190
  ```
119
191
 
120
- ## Usage
121
-
122
- Pass your HTML render function to `setHumidRenderer`
123
-
124
- ```javascript
125
- setHumidRenderer((json) => {
126
- const initialState = JSON.parse(json)
127
- return ReactDOMServer.renderToString(
128
- <Application initialPage={initialState}/>
129
- )
130
- })
131
- ```
192
+ ## Writing universal code
193
+ [Vue has an amazing resource][vue_ssr] on how to write universal code. Below
194
+ are a few highlights that are important to keep in mind.
132
195
 
133
- And finally call `render` from ERB.
196
+ ### State
134
197
 
135
- ```ruby
136
- <%= Humid.render(initial_state) %>
137
- ```
198
+ Humid uses a single context across multiple request. To avoid state pollution, we
199
+ provide a factory function to `setHumidRenderer` that builds a new app instance on
200
+ every call.
138
201
 
139
- ### Puma
202
+ This provides better isolation, but as it is still a shared context, polluting
203
+ `global` is still possible. Be careful of modifying `global` in your code.
140
204
 
141
- `mini_racer` is thread safe, but not fork safe. To use with web servers that
142
- employ forking, use `Humid.create_context` only on forked processes.
205
+ ### Polyfills
143
206
 
144
- ```ruby
145
- # Puma
146
- on_worker_boot do
147
- Humid.create_context
148
- end
207
+ Polyfills will fail when using in the `mini_racer` environment because of missing
208
+ browser APIs. Account for this by moving the `require` to `componentDidMount`
209
+ in your component.
149
210
 
150
- on_worker_shutdown do
151
- Humid.dispose
152
- end
211
+ ```
212
+ componentDidMount() {
213
+ const dialogPolyfill = require('dialog-polyfill').default
214
+ dialogPolyfill.registerDialog(this.dialog.current)
215
+ this.dialog.current.open = this.props.open
216
+ this.dialog.current.showModal()
217
+ }
153
218
  ```
154
219
 
155
220
  ## Contributing
@@ -177,3 +242,4 @@ See [our other projects][community] or
177
242
  [hire]: https://thoughtbot.com?utm_source=github
178
243
  [mini_racer]: https://github.com/rubyjs/mini_racer
179
244
  [webpacker]: https://github.com/rails/webpacker
245
+ [vue_ssr]: https://ssr.vuejs.org/
data/lib/humid.rb CHANGED
@@ -11,12 +11,24 @@ module Humid
11
11
  extend self
12
12
  include ActiveSupport::Configurable
13
13
 
14
- config_accessor :server_rendering_file do
14
+ class RenderError < StandardError
15
+ end
16
+
17
+ class FileNotFound < StandardError
18
+ end
19
+
20
+ @@context = nil
21
+
22
+ config_accessor :server_rendering_pack do
15
23
  "server_rendering.js"
16
24
  end
17
25
 
18
- config_accessor :server_rendering_source_map do
19
- nil
26
+ config_accessor :use_source_map do
27
+ false
28
+ end
29
+
30
+ config_accessor :raise_render_errors do
31
+ true
20
32
  end
21
33
 
22
34
  config_accessor :logger do
@@ -24,10 +36,7 @@ module Humid
24
36
  end
25
37
 
26
38
  config_accessor :context_options do
27
- {
28
- timeout: 1000,
29
- ensure_gc_after_idle: 2000
30
- }
39
+ {}
31
40
  end
32
41
 
33
42
  def remove_functions
@@ -54,16 +63,30 @@ module Humid
54
63
  JS
55
64
  end
56
65
 
57
- def context
58
- if @@context && Webpacker.env.development? && Webpacker.compiler.stale?
66
+ def handle_stale_files
67
+ if Webpacker.compiler.stale?
59
68
  Webpacker.compiler.compile
69
+ end
70
+
71
+ public_path = Webpacker.config.public_path
72
+ server_rendering_pack = config.server_rendering_pack
73
+ source_path = public_path.join(Webpacker.manifest.lookup(server_rendering_pack)[1..-1])
74
+ filename = File.basename(source_path.to_s)
75
+
76
+ if @@current_filename != filename
60
77
  dispose
61
78
  create_context
62
- else
63
- @@context
64
79
  end
65
80
  end
66
81
 
82
+ def context
83
+ if @@context && Webpacker.env.development?
84
+ handle_stale_files
85
+ end
86
+
87
+ @@context
88
+ end
89
+
67
90
  def dispose
68
91
  if @@context
69
92
  @@context.dispose
@@ -84,21 +107,39 @@ module Humid
84
107
  ctx.eval(js)
85
108
 
86
109
  public_path = Webpacker.config.public_path
87
- asset_path = public_path.join(Webpacker.manifest.lookup(config.server_rendering_file)[1..-1])
88
- filename = File.basename(asset_path.to_s)
89
- ctx.eval(File.read(asset_path), filename: filename)
90
110
 
91
- if config.server_rendering_source_map
92
- map_path = public_path.join(Webpacker.manifest.lookup(config.server_rendering_source_map)[1..-1])
111
+ webpack_source_file = Webpacker.manifest.lookup(config.server_rendering_pack)
112
+ if webpack_source_file.nil?
113
+ raise FileNotFound.new("Humid could not find a built pack for #{config.server_rendering_pack}")
114
+ end
115
+
116
+ if config.use_source_map
117
+ webpack_source_map = Webpacker.manifest.lookup("#{config.server_rendering_pack}.map")
118
+ map_path = public_path.join(webpack_source_map[1..-1])
93
119
  ctx.attach("readSourceMap", proc { File.read(map_path) })
94
120
  end
95
121
 
122
+ source_path = public_path.join(webpack_source_file[1..-1])
123
+ filename = File.basename(source_path.to_s)
124
+ @@current_filename = filename
125
+ ctx.eval(File.read(source_path), filename: filename)
126
+
96
127
  @@context = ctx
97
128
  end
98
129
 
99
130
  def render(*args)
100
131
  ActiveSupport::Notifications.instrument("render.humid") do
101
- @@context.call("__renderer", *args)
132
+ context.call("__renderer", *args)
133
+ rescue MiniRacer::RuntimeError => e
134
+ message = ([e.message] + e.backtrace.filter {|x| x.starts_with? "JavaScript"}).join("\n")
135
+ render_error = Humid::RenderError.new(message)
136
+
137
+ if config.raise_render_errors
138
+ raise render_error
139
+ else
140
+ config.logger.error(render_error.inspect)
141
+ ""
142
+ end
102
143
  end
103
144
  end
104
145
  end
data/lib/humid/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Humid
2
- VERSION = "0.0.1".freeze
2
+ VERSION = "0.0.5".freeze
3
3
  end
@@ -45,7 +45,7 @@ RSpec.describe Humid::LogSubscriber do
45
45
  end
46
46
 
47
47
  it "is attached" do
48
- allow(Humid.config).to receive("server_rendering_file") { "simple.js" }
48
+ allow(Humid.config).to receive("server_rendering_pack") { "simple.js" }
49
49
  Humid.create_context
50
50
  expect(Humid::LogSubscriber.runtime).to eql(0)
51
51
  Humid.render
data/spec/render_spec.rb CHANGED
@@ -14,14 +14,24 @@ RSpec.describe "Humid" do
14
14
  end
15
15
 
16
16
  it "creates a context with initial js" do
17
- allow(Humid.config).to receive("server_rendering_file") { "simple.js" }
17
+ allow(Humid.config).to receive("server_rendering_pack") { "simple.js" }
18
18
  Humid.create_context
19
19
 
20
20
  expect(Humid.context).to be_kind_of(MiniRacer::Context)
21
21
  end
22
22
 
23
+ context "When the file can not be found" do
24
+ it "raises" do
25
+ allow(Humid.config).to receive("server_rendering_pack") { "does_not_exist.js" }
26
+
27
+ expect {
28
+ Humid.create_context
29
+ }.to raise_error(Humid::FileNotFound, "Humid could not find a built pack for does_not_exist.js")
30
+ end
31
+ end
32
+
23
33
  it "does not have timeouts, immediates, and intervals" do
24
- allow(Humid.config).to receive("server_rendering_file") { "simple.js" }
34
+ allow(Humid.config).to receive("server_rendering_pack") { "simple.js" }
25
35
 
26
36
  Humid.create_context
27
37
 
@@ -43,7 +53,7 @@ RSpec.describe "Humid" do
43
53
  end
44
54
 
45
55
  it "proxies to Rails logger" do
46
- allow(Humid.config).to receive("server_rendering_file") { "simple.js" }
56
+ allow(Humid.config).to receive("server_rendering_pack") { "simple.js" }
47
57
  Humid.create_context
48
58
  expect(Humid.logger).to receive(:info).with("hello")
49
59
 
@@ -53,7 +63,7 @@ RSpec.describe "Humid" do
53
63
 
54
64
  describe "context" do
55
65
  it "returns the created context" do
56
- allow(Humid.config).to receive("server_rendering_file") { "simple.js" }
66
+ allow(Humid.config).to receive("server_rendering_pack") { "simple.js" }
57
67
 
58
68
  Humid.create_context
59
69
 
@@ -64,7 +74,7 @@ RSpec.describe "Humid" do
64
74
  it "does not recompile the JS" do
65
75
  allow(Webpacker).to receive_message_chain("env.development?") { false }
66
76
  allow(Webpacker).to receive_message_chain("compiler.stale?") { true }
67
- allow(Humid.config).to receive("server_rendering_file") { "simple.js" }
77
+ allow(Humid.config).to receive("server_rendering_pack") { "simple.js" }
68
78
 
69
79
  Humid.create_context
70
80
  prev_context = Humid.context
@@ -79,37 +89,61 @@ RSpec.describe "Humid" do
79
89
  end
80
90
  end
81
91
 
82
- context "when the js is stale and env is development" do
83
- it "compiles the JS" do
92
+ context "when the env is development" do
93
+ it "compiles the JS when stale" do
84
94
  allow(Webpacker).to receive_message_chain("env.development?") { true }
85
95
  allow(Webpacker).to receive_message_chain("compiler.stale?") { true }
86
96
  allow(Webpacker).to receive_message_chain("compiler.compile")
87
- allow(Humid.config).to receive("server_rendering_file") { "simple.js" }
97
+ allow(Humid.config).to receive("server_rendering_pack") { "simple.js" }
88
98
 
89
99
  Humid.create_context
90
100
  prev_context = Humid.context
91
101
  expect(prev_context).to be_kind_of(MiniRacer::Context)
92
102
 
93
103
  allow(Webpacker).to receive_message_chain("compiler.stale?") { true }
104
+ # This simulates a changing file
105
+ allow(Humid.config).to receive("server_rendering_pack") { "simple_changed.js" }
106
+
107
+ next_context = Humid.context
108
+
109
+ expect(prev_context).to_not eql(next_context)
110
+ expect(next_context).to be_kind_of(MiniRacer::Context)
111
+ end
112
+
113
+ it "creates a new context when webpack-devserver already handled JS staleness" do
114
+ allow(Webpacker).to receive_message_chain("env.development?") { true }
115
+ allow(Webpacker).to receive_message_chain("compiler.stale?") { true }
116
+ allow(Webpacker).to receive_message_chain("compiler.compile")
117
+ allow(Humid.config).to receive("server_rendering_pack") { "simple.js" }
118
+
119
+ Humid.create_context
120
+ prev_context = Humid.context
121
+ expect(Humid.render).to eql("hello")
122
+ expect(prev_context).to be_kind_of(MiniRacer::Context)
123
+
124
+ allow(Webpacker).to receive_message_chain("compiler.stale?") { false }
125
+ # This simulates a changing file
126
+ allow(Humid.config).to receive("server_rendering_pack") { "simple_changed.js" }
94
127
 
95
128
  next_context = Humid.context
96
129
 
97
130
  expect(prev_context).to_not eql(next_context)
98
131
  expect(next_context).to be_kind_of(MiniRacer::Context)
132
+ expect(Humid.render).to eql("hello changed")
99
133
  end
100
134
  end
101
135
  end
102
136
 
103
137
  describe "render" do
104
138
  it "returns a js output" do
105
- allow(Humid.config).to receive("server_rendering_file") { "simple.js" }
139
+ allow(Humid.config).to receive("server_rendering_pack") { "simple.js" }
106
140
  Humid.create_context
107
141
 
108
142
  expect(Humid.render).to eql("hello")
109
143
  end
110
144
 
111
145
  it "applys args to the func" do
112
- allow(Humid.config).to receive("server_rendering_file") { "args.js" }
146
+ allow(Humid.config).to receive("server_rendering_pack") { "args.js" }
113
147
  Humid.create_context
114
148
 
115
149
  args = ["a", 1, 2, [], {}]
@@ -118,22 +152,37 @@ RSpec.describe "Humid" do
118
152
  end
119
153
 
120
154
  it "can use source maps to see errors" do
121
- allow(Humid.config).to receive("server_rendering_file") { "reporting.js" }
122
- allow(Humid.config).to receive("server_rendering_source_map") {
123
- "reporting.js.map"
124
- }
155
+ allow(Humid.config).to receive("server_rendering_pack") { "reporting.js" }
156
+ allow(Humid.config).to receive("use_source_map") { true }
125
157
 
126
158
  Humid.create_context
127
159
 
128
160
  expect {
129
161
  Humid.render
130
162
  }.to raise_error { |error|
131
- expect(error).to be_a(MiniRacer::RuntimeError)
132
- expect(error.message).to eql("Error: ^^ Look! These stack traces map to the actual source code :)")
133
- expect(error.backtrace[0]).to eql("JavaScript at throwSomeError (/webpack:/app/javascript/packs/components/error-causing-component.js:2:1)")
134
- expect(error.backtrace[1]).to eql("JavaScript at ErrorCausingComponent (/webpack:/app/javascript/packs/components/error-causing-component.js:8:1)")
135
- expect(error.backtrace[2]).to eql("JavaScript at /webpack:/app/javascript/packs/reporting.js:18:1")
163
+ expect(error).to be_a(Humid::RenderError)
164
+ message = <<~MSG
165
+ Error: ^^ Look! These stack traces map to the actual source code :)
166
+ JavaScript at throwSomeError (/webpack:/app/javascript/packs/components/error-causing-component.js:2:1)
167
+ JavaScript at ErrorCausingComponent (/webpack:/app/javascript/packs/components/error-causing-component.js:8:1)
168
+ JavaScript at /webpack:/app/javascript/packs/reporting.js:18:1
169
+ MSG
170
+
171
+ expect(error.message).to eql message.strip
136
172
  }
137
173
  end
174
+
175
+ it "siliences render errors to the log" do
176
+ allow(Humid.config).to receive("server_rendering_pack") { "reporting.js" }
177
+ allow(Humid.config).to receive("raise_render_errors") { false }
178
+ allow(Humid.config).to receive("use_source_map") { true }
179
+
180
+ Humid.create_context
181
+
182
+ expect(Humid.logger).to receive(:error)
183
+ output = Humid.render
184
+
185
+ expect(output).to eql("")
186
+ end
138
187
  end
139
188
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: humid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johny Ho
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-10 00:00:00.000000000 Z
11
+ date: 2021-07-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: webpacker
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '6.1'
47
+ version: '6.0'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '6.1'
54
+ version: '6.0'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -100,14 +100,14 @@ dependencies:
100
100
  requirements:
101
101
  - - ">="
102
102
  - !ruby/object:Gem::Version
103
- version: '6.1'
103
+ version: '6.0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
- version: '6.1'
110
+ version: '6.0'
111
111
  description: Javascript SSR rendering for Rails
112
112
  email: jho406@gmail.com
113
113
  executables: []