rails-react-ssr 0.1.0
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 +7 -0
- data/CHANGELOG.md +0 -0
- data/README.md +202 -0
- data/lib/rails-react-ssr.rb +1 -0
- data/lib/rails_react_ssr.rb +7 -0
- data/lib/rails_react_ssr/errors.rb +24 -0
- data/lib/rails_react_ssr/server_runner.rb +120 -0
- data/lib/rails_react_ssr/version.rb +3 -0
- data/lib/rails_react_ssr/webpacker_utils.rb +73 -0
- data/test/server_runner_test.rb +47 -0
- data/test/test_app/Rakefile +3 -0
- data/test/test_app/app/javascript/packs/application.js +3 -0
- data/test/test_app/bin/webpack +14 -0
- data/test/test_app/bin/webpack-dev-server +14 -0
- data/test/test_app/config.ru +5 -0
- data/test/test_app/config/application.rb +12 -0
- data/test/test_app/config/environment.rb +4 -0
- data/test/test_app/config/webpack/development.js +0 -0
- data/test/test_app/config/webpacker.yml +97 -0
- data/test/test_app/config/webpacker_public_root.yml +19 -0
- data/test/test_app/log/development.log +12 -0
- data/test/test_app/package.json +13 -0
- data/test/test_app/public/packs/application-k344a6d59eef8632c9d1.js +3 -0
- data/test/test_app/public/packs/manifest.json +31 -0
- data/test/test_app/yarn.lock +11 -0
- data/test/test_helper.rb +38 -0
- data/test/webpacker_utils_test.rb +41 -0
- metadata +175 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3912c1b48d7d967fd867a88e16704ca98cd407d89d9ec0ce81c5d84c2fd975c5
|
4
|
+
data.tar.gz: 3874620976e632c57a7f05cdfdb72d9953f11218cc90a81b79aac51187e6b8db
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 390e781fb0622497b051d6d1cd52bf7f306c6de19a24bae542692b64680d760e5f7f4ba3a8371bec1fd3c287ed8f41e1f09f11afa08a502fe4928965cf1c10ad
|
7
|
+
data.tar.gz: 8eda42ff7e39306e8a5af2130d8db038cccb6725f346c59529586f59be1c2f0728d31a0992f1f31fa12a634e94a34dcf58c452d4da7ca73a98a04b5ee127967b
|
data/CHANGELOG.md
ADDED
File without changes
|
data/README.md
ADDED
@@ -0,0 +1,202 @@
|
|
1
|
+
# RailsReactSSR
|
2
|
+
|
3
|
+
RailsReactSSR is a light weight JS server side rendering utility that takes advantage of `Webpacker` and `NodeJS`.
|
4
|
+
|
5
|
+
## Motivation
|
6
|
+
|
7
|
+
In my latest project I designed my application to use Rails for my API endpoints and `ReactJS` with `react-router` to
|
8
|
+
handle routing and handle the front end. I needed a basic tool that would not add a lot of bloat and be able to handle
|
9
|
+
server side rendering while allowing me to process the response (i.e. handle redirects from the router).
|
10
|
+
|
11
|
+
## Dependencies
|
12
|
+
|
13
|
+
- [Ruby On Rails](https://rubyonrails.org/)
|
14
|
+
- [Webpacker](https://github.com/rails/webpacker)
|
15
|
+
- [NodeJS](https://nodejs.org/)
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
Add this line to your application's Gemfile:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem 'rails-react-ssr'
|
23
|
+
```
|
24
|
+
|
25
|
+
And then execute:
|
26
|
+
|
27
|
+
$ bundle install
|
28
|
+
|
29
|
+
Or install it yourself as:
|
30
|
+
|
31
|
+
$ gem install rails-react-ssr
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
`RailsReactSSR::ServerRunner.exec!(bundle, props:, outputTemp:, max_tries:, delay:)`
|
36
|
+
|
37
|
+
* `bundle` is the path or name of the bundle in the `app/javascript/packs` directory
|
38
|
+
|
39
|
+
(optional)
|
40
|
+
|
41
|
+
* `props` is a hash that will converted to a JSON plain object and passed to the server
|
42
|
+
* `outputTemp` is either:
|
43
|
+
* a boolean, where true will output the compiled server code to `tmp/ssr/[bundle].js`
|
44
|
+
* a string that is the full path to the file to write to
|
45
|
+
* `max_tries` is the number of retries when fetching the bundle from teh `webpack-dev-server`
|
46
|
+
* `delay` is the time in ms between each retry
|
47
|
+
|
48
|
+
#### Basic usage
|
49
|
+
|
50
|
+
##### `server.js`
|
51
|
+
```typescript jsx
|
52
|
+
// Some processing here
|
53
|
+
|
54
|
+
stdout(yourHtmlOutput);
|
55
|
+
```
|
56
|
+
|
57
|
+
##### Your controller
|
58
|
+
```ruby
|
59
|
+
def index
|
60
|
+
render html: RailsReactSSR::ServerRunner.exec!('server.js')
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
#### Passing properties to the server
|
65
|
+
|
66
|
+
##### From the controller:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
def index
|
70
|
+
render html: RailsReactSSR::ServerRunner.exec!('server.js', props: {current_user: current_user})
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
##### From the server code:
|
75
|
+
|
76
|
+
```javascript
|
77
|
+
...
|
78
|
+
|
79
|
+
// Do something with the user
|
80
|
+
console.log('Current user', serverProps.currentUser.username);
|
81
|
+
|
82
|
+
...
|
83
|
+
```
|
84
|
+
|
85
|
+
The keys in the properties passed to the server will be transformed to camelized strings.
|
86
|
+
|
87
|
+
#### Handling redirects with `React-Router-Web`
|
88
|
+
|
89
|
+
Below is an example of handling redirects with [`react-router`](https://reacttraining.com/react-router/).
|
90
|
+
The principle should be the same for any routing packages.
|
91
|
+
|
92
|
+
##### `server.js`
|
93
|
+
```typescript jsx
|
94
|
+
// Not the complete story
|
95
|
+
|
96
|
+
const context = {};
|
97
|
+
|
98
|
+
const RedirectWithStatus = ({from, to, status}) => {
|
99
|
+
return (
|
100
|
+
<Route
|
101
|
+
render={({ staticContext }) => {
|
102
|
+
// there is no `staticContext` on the client, so
|
103
|
+
// we need to guard against that here
|
104
|
+
if (staticContext) staticContext.status = status;
|
105
|
+
return <Redirect from={from} to={to} />;
|
106
|
+
}}
|
107
|
+
/>
|
108
|
+
);
|
109
|
+
}
|
110
|
+
|
111
|
+
const markup = ReactDOMServer.renderToString(
|
112
|
+
<StaticRouter location={serverProps.location} context={context}>
|
113
|
+
<Switch>
|
114
|
+
<RedirectWithStatus
|
115
|
+
status={301}
|
116
|
+
from="/users"
|
117
|
+
to="/profiles" />
|
118
|
+
<RedirectWithStatus
|
119
|
+
status={302}
|
120
|
+
from="/courses"
|
121
|
+
to="/dashboard"
|
122
|
+
/>
|
123
|
+
</Switch>
|
124
|
+
</StaticRouter>
|
125
|
+
);
|
126
|
+
|
127
|
+
const output = {
|
128
|
+
html: markup,
|
129
|
+
logs: recordedLogs,
|
130
|
+
redirect: context.url,
|
131
|
+
status: context.status
|
132
|
+
};
|
133
|
+
|
134
|
+
stdout(JSON.stringify(output));
|
135
|
+
```
|
136
|
+
More details on SSR and `react-router` at https://reacttraining.com/react-router/web/guides/server-rendering
|
137
|
+
|
138
|
+
##### Your controller
|
139
|
+
```ruby
|
140
|
+
def index
|
141
|
+
output = RailsReactSSR::ServerRunner.exec!('server.js', props: {current_user: current_user, location: request.fullpath})
|
142
|
+
|
143
|
+
react_response = ActiveSupport::JSON.decode output.split(/[\r\n]+/).reject(&:empty?).last
|
144
|
+
|
145
|
+
react_response.deep_symbolize_keys!
|
146
|
+
|
147
|
+
if react_response[:redirect]
|
148
|
+
redirect_to react_response[:redirect], status: 302
|
149
|
+
else
|
150
|
+
render html: react_response[:html]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
```
|
154
|
+
|
155
|
+
### Caching Example
|
156
|
+
|
157
|
+
To improve the response time from the server, you should consider caching.
|
158
|
+
|
159
|
+
Things to consider:
|
160
|
+
1) Using a cache key that is not the same for every route if you are using a JS routing package.
|
161
|
+
2) How large the response is form the JS server.
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
|
165
|
+
def index
|
166
|
+
## Do something to the path to generate a key that represents it in the server routes
|
167
|
+
cache_key = generate_cache_key_from_uri request.fullpath
|
168
|
+
|
169
|
+
output = Rails.cache.fetch cache_key, expires: 12.hours, race_condition_ttl: 1.minute, namespace: :react_server do
|
170
|
+
RailsReactSSR::ServerRunner.exec!('server.js', props: {current_user: current_user, location: request.fullpath})
|
171
|
+
end
|
172
|
+
|
173
|
+
handle_server_response output
|
174
|
+
end
|
175
|
+
|
176
|
+
```
|
177
|
+
|
178
|
+
## Alternatives
|
179
|
+
|
180
|
+
There are several alternatives that are more comprehensive and might be a better fit for your use case:
|
181
|
+
|
182
|
+
1) [ReactOnRails](https://github.com/shakacode/react_on_rails)
|
183
|
+
2) [react-rails](https://github.com/reactjs/react-rails)
|
184
|
+
3) [reactssr-rails](https://github.com/towry/reactssr-rails)
|
185
|
+
|
186
|
+
## Issues
|
187
|
+
|
188
|
+
Report bugs at https://github.com/jefawks3/rails-react-ssr.
|
189
|
+
Please make sure to include how to reproduce the issue, otherwise it might be ignored.
|
190
|
+
|
191
|
+
## Contributing
|
192
|
+
|
193
|
+
1) Fork it (https://github.com/jefawks3/rails-react-ssr)
|
194
|
+
2) Create your feature branch (git checkout -b my-new-feature)
|
195
|
+
3) Commit your changes (git commit -am 'Add some feature')
|
196
|
+
4) Push to the branch (git push origin my-new-feature)
|
197
|
+
5) Create a new Pull Request
|
198
|
+
|
199
|
+
## License
|
200
|
+
|
201
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
202
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'rails_react_ssr'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module RailsReactSSR
|
2
|
+
# RailsReactSSR error
|
3
|
+
class Error < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
# Bundle errors
|
7
|
+
class BundleError < Error
|
8
|
+
attr_reader :bundle
|
9
|
+
|
10
|
+
def initialize(bundle, *args)
|
11
|
+
super *args
|
12
|
+
|
13
|
+
@bundle = bundle
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Missing bundle package
|
18
|
+
class MissingBundleError < BundleError
|
19
|
+
end
|
20
|
+
|
21
|
+
# Execution Error
|
22
|
+
class ExecutionError < BundleError
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'webpacker'
|
2
|
+
require 'shellwords'
|
3
|
+
require 'active_support/json'
|
4
|
+
require 'open-uri'
|
5
|
+
require 'rails'
|
6
|
+
require 'open3'
|
7
|
+
|
8
|
+
module RailsReactSSR
|
9
|
+
##
|
10
|
+
# Executes the ReactJS package using NodeJS that was built using webpacker
|
11
|
+
class ServerRunner
|
12
|
+
##
|
13
|
+
# Redirect console output to be logged by an array
|
14
|
+
CONSOLE_POLYFILL = <<-CONSOLE_POLYFILL
|
15
|
+
const stdout = console.log;
|
16
|
+
const stderr = console.error;
|
17
|
+
|
18
|
+
const recordedLogs = [];
|
19
|
+
|
20
|
+
['log', 'info', 'debug', 'warn', 'error'].forEach(level => {
|
21
|
+
console[level] = (...args) => {
|
22
|
+
recordedLogs.push({ level: level, args: args });
|
23
|
+
}
|
24
|
+
});
|
25
|
+
CONSOLE_POLYFILL
|
26
|
+
|
27
|
+
##
|
28
|
+
# Execute the bundled package
|
29
|
+
#
|
30
|
+
# <tt>:props</tt> - The properties to pass to the server JS code
|
31
|
+
# <tt>:outputTemp</tt> - If true, output the compiled bundle to the tmp/ssr directory, pass a string to specify the
|
32
|
+
# output file
|
33
|
+
# <tt>:max_tries</tt> - The number of tries when getting the bundle from the webpack dev server
|
34
|
+
# <tt>:delay</tt> - The delay in ms between tries
|
35
|
+
def self.exec!(bundle, props: {}, outputTemp: false, max_tries: 10, delay: 1000)
|
36
|
+
bundle_file = RailsReactSSR::WebpackerUtils.open_bundle bundle, max_tries: max_tries, delay: delay
|
37
|
+
|
38
|
+
## Format the properties for js
|
39
|
+
jsProps = props.inject({}) do |hash,(k,v)|
|
40
|
+
hash[k.to_s.camelcase.gsub(/\A./, &:downcase)] = v
|
41
|
+
hash
|
42
|
+
end
|
43
|
+
|
44
|
+
status = 0
|
45
|
+
output = nil
|
46
|
+
|
47
|
+
begin
|
48
|
+
js = Tempfile.new [File.basename(bundle_file, '.*'), File.extname(bundle_file)]
|
49
|
+
|
50
|
+
begin
|
51
|
+
write_console_polyfill js
|
52
|
+
write_props_polyfill js, jsProps
|
53
|
+
write_bundle js, bundle_file
|
54
|
+
|
55
|
+
js.flush
|
56
|
+
|
57
|
+
if outputTemp
|
58
|
+
outputTemp = Rails.root.join('tmp/ssr/', bundle) if outputTemp.is_a? TrueClass
|
59
|
+
|
60
|
+
Rails.logger.debug "Coping server bundle to #{outputTemp}"
|
61
|
+
IO.copy_stream js.path, outputTemp
|
62
|
+
end
|
63
|
+
|
64
|
+
status, output = exec_contents js
|
65
|
+
ensure
|
66
|
+
js.unlink
|
67
|
+
end
|
68
|
+
rescue => e
|
69
|
+
Rails.logger.error "Unable to execute the bundle '#{bundle}': #{e.message}"
|
70
|
+
raise RailsReactSSR::ExecutionError.new(bundle, "Unable to run the bundle '#{bundle}'")
|
71
|
+
ensure
|
72
|
+
bundle_file.close
|
73
|
+
end
|
74
|
+
|
75
|
+
raise RailsReactSSR::ExecutionError.new(bundle,"Unable to execute the server bundle #{bundle}") unless status.zero?
|
76
|
+
|
77
|
+
output
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def self.exec_contents(file)
|
83
|
+
output = error = ''
|
84
|
+
|
85
|
+
cmd = ['node', Shellwords.escape(file.path)]
|
86
|
+
|
87
|
+
cmd_str = cmd.join ' '
|
88
|
+
|
89
|
+
status = Open3.popen3 cmd_str do |inp, out, err, thr|
|
90
|
+
output = out.read
|
91
|
+
error = err.read
|
92
|
+
|
93
|
+
Rails.logger.info "[#{thr.value.exitstatus}}] #{cmd_str}"
|
94
|
+
Rails.logger.debug output
|
95
|
+
Rails.logger.error error unless error.nil? || error.empty?
|
96
|
+
|
97
|
+
thr.value.exitstatus
|
98
|
+
end
|
99
|
+
|
100
|
+
[status, output]
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.write_props_polyfill(temp_file, props)
|
104
|
+
temp_file.write <<-JS
|
105
|
+
const serverProps = #{ActiveSupport::JSON.encode props};
|
106
|
+
|
107
|
+
JS
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.write_console_polyfill(temp_file)
|
111
|
+
temp_file.write CONSOLE_POLYFILL
|
112
|
+
temp_file.write "\n\n"
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.write_bundle(temp_file, bundle_file)
|
116
|
+
IO.copy_stream bundle_file, temp_file
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module RailsReactSSR
|
4
|
+
class WebpackerUtils
|
5
|
+
##
|
6
|
+
# Return the hashed name from the +bundle+
|
7
|
+
def self.hashed_bundle_name!(bundle)
|
8
|
+
Webpacker.manifest.lookup! bundle
|
9
|
+
rescue Webpacker::Manifest::MissingEntryError
|
10
|
+
raise RailsReactSSR::MissingBundleError.new(bundle, "The ReactJS package '#{bundle}' is missing from the manifest.json file.")
|
11
|
+
end
|
12
|
+
|
13
|
+
##
|
14
|
+
# Open the +bundle+ file for reading
|
15
|
+
#
|
16
|
+
# Returns IO stream with the +bundle+ contents. If +bundle+ cannot be found,
|
17
|
+
# raises +RailsReactSSR::MissingBundleError+
|
18
|
+
def self.open_bundle(bundle, max_tries: 10, delay: 1000)
|
19
|
+
hashed = hashed_bundle_name! bundle
|
20
|
+
|
21
|
+
if Webpacker.dev_server.running?
|
22
|
+
dev_server_bundle hashed, max_tries, delay
|
23
|
+
else
|
24
|
+
local_file_bundle hashed
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def self.dev_bundle_uri(path)
|
31
|
+
URI::Generic.new(
|
32
|
+
Webpacker.dev_server.protocol,
|
33
|
+
nil,
|
34
|
+
Webpacker.dev_server.host,
|
35
|
+
Webpacker.dev_server.port,
|
36
|
+
nil,
|
37
|
+
path,
|
38
|
+
nil,
|
39
|
+
nil,
|
40
|
+
nil
|
41
|
+
).to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.bundle_fullpath(path)
|
45
|
+
File.join Rails.root, 'public', path
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.dev_server_bundle(hashed_bundle, max_tries, delay, tries = 0)
|
49
|
+
tries += 1
|
50
|
+
|
51
|
+
uri = self.dev_bundle_uri hashed_bundle
|
52
|
+
|
53
|
+
Rails.logger.debug "Reading remote bundle #{uri}"
|
54
|
+
|
55
|
+
open uri
|
56
|
+
rescue OpenURI::HTTPError => e
|
57
|
+
# On the first page hit my not be available on the dev server so we need to wait for it to compile
|
58
|
+
if tries < max_tries
|
59
|
+
Rails.logger.debug "The remote bundle is not ready trying again in #{delay}ms - #{tries} of #{max_tries}"
|
60
|
+
sleep delay / 1000
|
61
|
+
retry
|
62
|
+
else
|
63
|
+
raise e
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.local_file_bundle(hashed_bundle)
|
68
|
+
full_path = File.join Rails.root, 'public', hashed_bundle
|
69
|
+
|
70
|
+
File.open full_path, 'rb'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class RailsReactSSR::ServerRunnerTest < RailsReactSSR::Test
|
4
|
+
def setup
|
5
|
+
# Do nothing
|
6
|
+
end
|
7
|
+
|
8
|
+
def teardown
|
9
|
+
# Do nothing
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_application_temp_output
|
13
|
+
tempFile = File.expand_path 'tmp/output.js'
|
14
|
+
|
15
|
+
File.unlink tempFile if File.exists? tempFile
|
16
|
+
|
17
|
+
RailsReactSSR::ServerRunner.exec! 'application.js', outputTemp: tempFile
|
18
|
+
|
19
|
+
assert_equal File.read(tempFile), <<-OUTPUT
|
20
|
+
const stdout = console.log;
|
21
|
+
const stderr = console.error;
|
22
|
+
|
23
|
+
const recordedLogs = [];
|
24
|
+
|
25
|
+
['log', 'info', 'debug', 'warn', 'error'].forEach(level => {
|
26
|
+
console[level] = (...args) => {
|
27
|
+
recordedLogs.push({ level: level, args: args });
|
28
|
+
}
|
29
|
+
});
|
30
|
+
|
31
|
+
|
32
|
+
const serverProps = {};
|
33
|
+
|
34
|
+
console.log('Hello World from Webpacker');
|
35
|
+
|
36
|
+
stdout('<html><body>Hello from the server</body></html>');
|
37
|
+
OUTPUT
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_application_output
|
41
|
+
output = RailsReactSSR::ServerRunner.exec! 'application.js'
|
42
|
+
|
43
|
+
assert_equal output, <<-OUTPUT
|
44
|
+
<html><body>Hello from the server</body></html>
|
45
|
+
OUTPUT
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
|
4
|
+
ENV["NODE_ENV"] ||= ENV["RAILS_ENV"]
|
5
|
+
|
6
|
+
require "pathname"
|
7
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
8
|
+
Pathname.new(__FILE__).realpath)
|
9
|
+
|
10
|
+
require "bundler/setup"
|
11
|
+
|
12
|
+
require "webpacker"
|
13
|
+
require "webpacker/webpack_runner"
|
14
|
+
Webpacker::WebpackRunner.run(ARGV)
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
|
4
|
+
ENV["NODE_ENV"] ||= ENV["RAILS_ENV"]
|
5
|
+
|
6
|
+
require "pathname"
|
7
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
8
|
+
Pathname.new(__FILE__).realpath)
|
9
|
+
|
10
|
+
require "bundler/setup"
|
11
|
+
|
12
|
+
require "webpacker"
|
13
|
+
require "webpacker/dev_server_runner"
|
14
|
+
Webpacker::DevServerRunner.run(ARGV)
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "action_controller/railtie"
|
2
|
+
require "action_view/railtie"
|
3
|
+
require "webpacker"
|
4
|
+
|
5
|
+
module TestApp
|
6
|
+
class Application < ::Rails::Application
|
7
|
+
config.secret_key_base = "abcdef"
|
8
|
+
config.eager_load = true
|
9
|
+
config.webpacker.check_yarn_integrity = false
|
10
|
+
config.active_support.test_order = :sorted
|
11
|
+
end
|
12
|
+
end
|
File without changes
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# Note: You must restart bin/webpack-dev-server for changes to take effect
|
2
|
+
|
3
|
+
default: &default
|
4
|
+
source_path: app/javascript
|
5
|
+
source_entry_path: packs
|
6
|
+
public_root_path: public
|
7
|
+
public_output_path: packs
|
8
|
+
cache_path: tmp/cache/webpacker
|
9
|
+
webpack_compile_output: false
|
10
|
+
|
11
|
+
# Additional paths webpack should lookup modules
|
12
|
+
# ['app/assets', 'engine/foo/app/assets']
|
13
|
+
resolved_paths:
|
14
|
+
- app/assets
|
15
|
+
- /etc/yarn
|
16
|
+
|
17
|
+
# Reload manifest.json on all requests so we reload latest compiled packs
|
18
|
+
cache_manifest: false
|
19
|
+
|
20
|
+
# Extract and emit a css file
|
21
|
+
extract_css: false
|
22
|
+
|
23
|
+
static_assets_extensions:
|
24
|
+
- .jpg
|
25
|
+
- .jpeg
|
26
|
+
- .png
|
27
|
+
- .gif
|
28
|
+
- .tiff
|
29
|
+
- .ico
|
30
|
+
- .svg
|
31
|
+
|
32
|
+
extensions:
|
33
|
+
- .mjs
|
34
|
+
- .js
|
35
|
+
- .sass
|
36
|
+
- .scss
|
37
|
+
- .css
|
38
|
+
- .module.sass
|
39
|
+
- .module.scss
|
40
|
+
- .module.css
|
41
|
+
- .png
|
42
|
+
- .svg
|
43
|
+
- .gif
|
44
|
+
- .jpeg
|
45
|
+
- .jpg
|
46
|
+
|
47
|
+
development:
|
48
|
+
<<: *default
|
49
|
+
compile: true
|
50
|
+
|
51
|
+
# Reference: https://webpack.js.org/configuration/dev-server/
|
52
|
+
dev_server:
|
53
|
+
https: false
|
54
|
+
host: localhost
|
55
|
+
port: 3035
|
56
|
+
public: localhost:3035
|
57
|
+
hmr: false
|
58
|
+
# Inline should be set to true if using HMR
|
59
|
+
inline: true
|
60
|
+
overlay: true
|
61
|
+
disable_host_check: true
|
62
|
+
use_local_ip: false
|
63
|
+
pretty: false
|
64
|
+
|
65
|
+
test:
|
66
|
+
<<: *default
|
67
|
+
compile: true
|
68
|
+
|
69
|
+
# Compile test packs to a separate directory
|
70
|
+
public_output_path: packs-test
|
71
|
+
|
72
|
+
production:
|
73
|
+
<<: *default
|
74
|
+
|
75
|
+
# Production depends on precompilation of packs prior to booting for performance.
|
76
|
+
compile: false
|
77
|
+
|
78
|
+
# Extract and emit a css file
|
79
|
+
extract_css: true
|
80
|
+
|
81
|
+
# Cache manifest.json for performance
|
82
|
+
cache_manifest: true
|
83
|
+
|
84
|
+
staging:
|
85
|
+
<<: *default
|
86
|
+
|
87
|
+
# Production depends on precompilation of packs prior to booting for performance.
|
88
|
+
compile: false
|
89
|
+
|
90
|
+
# Extract and emit a css file
|
91
|
+
extract_css: true
|
92
|
+
|
93
|
+
# Cache manifest.json for performance
|
94
|
+
cache_manifest: true
|
95
|
+
|
96
|
+
# Compile staging packs to a separate directory
|
97
|
+
public_output_path: packs-staging
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Note: You must restart bin/webpack-dev-server for changes to take effect
|
2
|
+
|
3
|
+
default: &default
|
4
|
+
public_root_path: ../public
|
5
|
+
|
6
|
+
development:
|
7
|
+
<<: *default
|
8
|
+
compile: true
|
9
|
+
|
10
|
+
test:
|
11
|
+
<<: *default
|
12
|
+
compile: true
|
13
|
+
public_output_path: packs-test
|
14
|
+
|
15
|
+
production:
|
16
|
+
<<: *default
|
17
|
+
compile: false
|
18
|
+
extract_css: true
|
19
|
+
cache_manifest: true
|
@@ -0,0 +1,12 @@
|
|
1
|
+
The remote bundle is not ready trying again in 1000ms - 1 of 10
|
2
|
+
The remote bundle is not ready trying again in 1000ms - 1 of 10
|
3
|
+
The remote bundle is not ready trying again in 1000ms - 1 of 10
|
4
|
+
The remote bundle is not ready trying again in 1000ms - 1 of 10
|
5
|
+
Reading remote bundle http://localhost:3035/packs/application-k344a6d59eef8632c9d1.js
|
6
|
+
The remote bundle is not ready trying again in 1000ms - 1 of 10
|
7
|
+
Reading remote bundle http://localhost:3035/packs/application-k344a6d59eef8632c9d1.js
|
8
|
+
The remote bundle is not ready trying again in 1000ms - 1 of 10
|
9
|
+
[Webpacker] Compiling…
|
10
|
+
[Webpacker] Compilation failed:
|
11
|
+
error Command "webpack" not found.
|
12
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
{
|
2
|
+
"bootstrap.css": "/packs/bootstrap-c38deda30895059837cf.css",
|
3
|
+
"application.css": "/packs/application-dd6b1cd38bfa093df600.css",
|
4
|
+
"bootstrap.js": "/packs/bootstrap-300631c4f0e0f9c865bc.js",
|
5
|
+
"application.js": "/packs/application-k344a6d59eef8632c9d1.js",
|
6
|
+
"application.png": "/packs/application-k344a6d59eef8632c9d1.png",
|
7
|
+
"fonts/fa-regular-400.woff2": "/packs/fonts/fa-regular-400-944fb546bd7018b07190a32244f67dc9.woff2",
|
8
|
+
"media/images/image.jpg": "/packs/media/images/image-c38deda30895059837cf.jpg",
|
9
|
+
"media/images/nested/image.jpg": "/packs/media/images/nested/image-c38deda30895059837cf.jpg",
|
10
|
+
"media/images/mb-icon.png": "/packs/media/images/mb-icon-c38deda30895059837cf.png",
|
11
|
+
"media/images/nested/mb-icon.png": "/packs/media/images/nested/mb-icon-c38deda30895059837cf.png",
|
12
|
+
"entrypoints": {
|
13
|
+
"application": {
|
14
|
+
"js": [
|
15
|
+
"/packs/vendors~application~bootstrap-c20632e7baf2c81200d3.chunk.js",
|
16
|
+
"/packs/vendors~application-e55f2aae30c07fb6d82a.chunk.js",
|
17
|
+
"/packs/application-k344a6d59eef8632c9d1.js"
|
18
|
+
],
|
19
|
+
"css": [
|
20
|
+
"/packs/1-c20632e7baf2c81200d3.chunk.css",
|
21
|
+
"/packs/application-k344a6d59eef8632c9d1.chunk.css"
|
22
|
+
]
|
23
|
+
},
|
24
|
+
"hello_stimulus": {
|
25
|
+
"css": [
|
26
|
+
"/packs/1-c20632e7baf2c81200d3.chunk.css",
|
27
|
+
"/packs/hello_stimulus-k344a6d59eef8632c9d1.chunk.css"
|
28
|
+
]
|
29
|
+
}
|
30
|
+
}
|
31
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
2
|
+
# yarn lockfile v1
|
3
|
+
|
4
|
+
|
5
|
+
left-pad@^1.2.0:
|
6
|
+
version "1.2.0"
|
7
|
+
resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.2.0.tgz#d30a73c6b8201d8f7d8e7956ba9616087a68e0ee"
|
8
|
+
|
9
|
+
right-pad@^1.0.1:
|
10
|
+
version "1.0.1"
|
11
|
+
resolved "https://registry.yarnpkg.com/right-pad/-/right-pad-1.0.1.tgz#8ca08c2cbb5b55e74dafa96bf7fd1a27d568c8d0"
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
|
3
|
+
require "rails"
|
4
|
+
require "rails/test_help"
|
5
|
+
require "byebug"
|
6
|
+
require 'rails-react-ssr'
|
7
|
+
|
8
|
+
require_relative "test_app/config/environment"
|
9
|
+
|
10
|
+
Rails.env = "production"
|
11
|
+
|
12
|
+
Rails.logger = Logger.new(STDOUT)
|
13
|
+
Rails.logger.level = Logger::DEBUG
|
14
|
+
|
15
|
+
|
16
|
+
Webpacker.instance = ::Webpacker::Instance.new
|
17
|
+
|
18
|
+
class RailsReactSSR::Test < Minitest::Test
|
19
|
+
private
|
20
|
+
def reloaded_config
|
21
|
+
Webpacker.instance.instance_variable_set(:@env, nil)
|
22
|
+
Webpacker.instance.instance_variable_set(:@config, nil)
|
23
|
+
Webpacker.instance.instance_variable_set(:@dev_server, nil)
|
24
|
+
Webpacker.env
|
25
|
+
Webpacker.config
|
26
|
+
Webpacker.dev_server
|
27
|
+
end
|
28
|
+
|
29
|
+
def with_rails_env(env)
|
30
|
+
original = Rails.env
|
31
|
+
Rails.env = ActiveSupport::StringInquirer.new(env)
|
32
|
+
reloaded_config
|
33
|
+
yield
|
34
|
+
ensure
|
35
|
+
Rails.env = ActiveSupport::StringInquirer.new(original)
|
36
|
+
reloaded_config
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'webpacker/dev_server_runner'
|
3
|
+
|
4
|
+
class RailsReactSSR::WebpackerUtilsTest < RailsReactSSR::Test
|
5
|
+
def test_bundle_not_found!
|
6
|
+
error = assert_raises RailsReactSSR::MissingBundleError do
|
7
|
+
RailsReactSSR::WebpackerUtils.hashed_bundle_name! 'missing.js'
|
8
|
+
end
|
9
|
+
|
10
|
+
assert_match 'missing.js', error.bundle
|
11
|
+
assert_match "The ReactJS package 'missing.js' is missing from the manifest.json file.", error.message
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_bundle_found!
|
15
|
+
assert_equal RailsReactSSR::WebpackerUtils.hashed_bundle_name!('application.js'),
|
16
|
+
"/packs/application-k344a6d59eef8632c9d1.js"
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_open_local_file
|
20
|
+
io = RailsReactSSR::WebpackerUtils.open_bundle 'application.js'
|
21
|
+
|
22
|
+
refute Webpacker.dev_server.running?
|
23
|
+
|
24
|
+
assert_equal io.read, raw_application_js
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_open_remote_file
|
28
|
+
# TODO Run dev server during tests to make sure remote file is accessible
|
29
|
+
skip 'Need to find a way to run the dev server during the tests'
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def raw_application_js
|
35
|
+
<<-AppplicaitonJS
|
36
|
+
console.log('Hello World from Webpacker');
|
37
|
+
|
38
|
+
stdout('<html><body>Hello from the server</body></html>');
|
39
|
+
AppplicaitonJS
|
40
|
+
end
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rails-react-ssr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- James Fawks
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-01-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: webpacker
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.0.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.0.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 5.2.1
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 5.2.1
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.17'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.17'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '10.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '10.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '5.14'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '5.14'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: byebug
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '11.1'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '11.1'
|
97
|
+
description:
|
98
|
+
email:
|
99
|
+
- jefawks3@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- CHANGELOG.md
|
105
|
+
- README.md
|
106
|
+
- lib/rails-react-ssr.rb
|
107
|
+
- lib/rails_react_ssr.rb
|
108
|
+
- lib/rails_react_ssr/errors.rb
|
109
|
+
- lib/rails_react_ssr/server_runner.rb
|
110
|
+
- lib/rails_react_ssr/version.rb
|
111
|
+
- lib/rails_react_ssr/webpacker_utils.rb
|
112
|
+
- test/server_runner_test.rb
|
113
|
+
- test/test_app/Rakefile
|
114
|
+
- test/test_app/app/javascript/packs/application.js
|
115
|
+
- test/test_app/bin/webpack
|
116
|
+
- test/test_app/bin/webpack-dev-server
|
117
|
+
- test/test_app/config.ru
|
118
|
+
- test/test_app/config/application.rb
|
119
|
+
- test/test_app/config/environment.rb
|
120
|
+
- test/test_app/config/webpack/development.js
|
121
|
+
- test/test_app/config/webpacker.yml
|
122
|
+
- test/test_app/config/webpacker_public_root.yml
|
123
|
+
- test/test_app/log/development.log
|
124
|
+
- test/test_app/package.json
|
125
|
+
- test/test_app/public/packs/application-k344a6d59eef8632c9d1.js
|
126
|
+
- test/test_app/public/packs/manifest.json
|
127
|
+
- test/test_app/yarn.lock
|
128
|
+
- test/test_helper.rb
|
129
|
+
- test/webpacker_utils_test.rb
|
130
|
+
homepage: https://github.com/jefawks3/rails-react-ssr
|
131
|
+
licenses:
|
132
|
+
- MIT
|
133
|
+
metadata:
|
134
|
+
homepage_uri: https://github.com/jefawks3/rails-react-ssr
|
135
|
+
source_code_uri: https://github.com/jefawks3/rails-react-ssr
|
136
|
+
changelog_uri: https://github.com/jefawks3/rails-react-ssr/blob/master/CHANGELOG.md
|
137
|
+
post_install_message:
|
138
|
+
rdoc_options: []
|
139
|
+
require_paths:
|
140
|
+
- lib
|
141
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: 2.2.0
|
146
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
requirements: []
|
152
|
+
rubygems_version: 3.0.6
|
153
|
+
signing_key:
|
154
|
+
specification_version: 4
|
155
|
+
summary: Light weight React SSR (Server Side Rendering) integration for Ruby on Rails,
|
156
|
+
Webpacker and NodeJS
|
157
|
+
test_files:
|
158
|
+
- test/test_app/app/javascript/packs/application.js
|
159
|
+
- test/test_app/bin/webpack
|
160
|
+
- test/test_app/bin/webpack-dev-server
|
161
|
+
- test/test_app/config/environment.rb
|
162
|
+
- test/test_app/config/webpacker_public_root.yml
|
163
|
+
- test/test_app/config/application.rb
|
164
|
+
- test/test_app/config/webpack/development.js
|
165
|
+
- test/test_app/config/webpacker.yml
|
166
|
+
- test/test_app/config.ru
|
167
|
+
- test/test_app/Rakefile
|
168
|
+
- test/test_app/yarn.lock
|
169
|
+
- test/test_app/public/packs/manifest.json
|
170
|
+
- test/test_app/public/packs/application-k344a6d59eef8632c9d1.js
|
171
|
+
- test/test_app/package.json
|
172
|
+
- test/test_app/log/development.log
|
173
|
+
- test/server_runner_test.rb
|
174
|
+
- test/webpacker_utils_test.rb
|
175
|
+
- test/test_helper.rb
|