rails-react-ssr 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|