Skip to content

Commit 2c4e00e

Browse files
committed
Improve EnsureAssetsCompiled logic to wait for webpack process to finish
Because there may be situations where a user is running a webpack process in watch mode in the background that will recompile the assets, it is possible that if the build time is extremely long, the user could start tests before the build completes. In this case, EnsureAssetsCompiled would start compiling the assets again even though there is already a webpack process doing that. This commit adds behavior to EnsureAssetsCompiled to check whether or not the webpack process is running in the background, and will defer the responsibility of compilation to that process, re-checking the compiled assets every second until they are up to date.
1 parent c271a57 commit 2c4e00e

File tree

7 files changed

+140
-42
lines changed

7 files changed

+140
-42
lines changed

lib/react_on_rails/configuration.rb

+10-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
21
module ReactOnRails
32
def self.configure
43
yield(configuration)
54
end
65

6+
DEFAULT_GENERATED_ASSETS_DIRS = [
7+
%w(app assets javascripts generated),
8+
%w(app assets javascripts stylesheets generated)
9+
].map { |dirs| File.join(*dirs) }.freeze
10+
711
def self.configuration
812
@configuration ||= Configuration.new(
9-
server_bundle_js_file: "app/assets/javascripts/generated/server.js",
13+
generated_assets_dirs: DEFAULT_GENERATED_ASSETS_DIRS,
14+
server_bundle_js_file: File.join(*%w(app assets javascripts generated server.js)),
1015
prerender: false,
1116
replay_console: true,
1217
logging_on_server: true,
@@ -23,17 +28,18 @@ class Configuration
2328
:trace, :development_mode,
2429
:logging_on_server, :server_renderer_pool_size,
2530
:server_renderer_timeout, :raise_on_prerender_error,
26-
:skip_display_none
31+
:skip_display_none, :generated_assets_dirs
2732

2833
def initialize(server_bundle_js_file: nil, prerender: nil, replay_console: nil,
2934
trace: nil, development_mode: nil,
3035
logging_on_server: nil, server_renderer_pool_size: nil,
3136
server_renderer_timeout: nil, raise_on_prerender_error: nil,
32-
skip_display_none: nil)
37+
skip_display_none: nil, generated_assets_dirs: DEFAULT_GENERATED_ASSETS_DIRS)
3338
self.server_bundle_js_file = if File.exist?(server_bundle_js_file)
3439
server_bundle_js_file
3540
end
3641

42+
self.generated_assets_dirs = generated_assets_dirs
3743
self.prerender = prerender
3844
self.replay_console = replay_console
3945
self.logging_on_server = logging_on_server
+65-15
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,50 @@
11
module ReactOnRails
22
class EnsureAssetsCompiled
3-
COMPILED_DIR_NAMES = %w(javascripts stylesheets fonts images).freeze
3+
class << self
4+
attr_accessor :has_been_run
5+
@has_been_run = false
6+
end
47

58
def self.build
69
client_dir = Rails.root.join("client")
7-
compiled_dirs = COMPILED_DIR_NAMES.map { |dir| Rails.root.join("app", "assets", dir, "generated") }
8-
checker = WebpackAssetsStatusChecker.new(client_dir: client_dir, compiled_dirs: compiled_dirs)
10+
compiled_dirs = ReactOnRails.configuration.generated_assets_dirs
11+
12+
assets_checker = WebpackAssetsStatusChecker.new(client_dir: client_dir, compiled_dirs: compiled_dirs)
13+
process_checker = WebpackProcessChecker.new
914
compiler = WebpackAssetsCompiler.new
10-
new(checker, compiler)
15+
16+
new(assets_checker, compiler, process_checker)
1117
end
1218

13-
attr_reader :webpack_assets_checker, :webpack_assets_compiler, :assets_have_been_compiled
19+
attr_reader :webpack_assets_checker, :webpack_assets_compiler, :webpack_process_checker, :assets_have_been_compiled
1420

15-
def initialize(webpack_assets_checker, webpack_assets_compiler)
21+
def initialize(webpack_assets_checker, webpack_assets_compiler, webpack_process_checker)
1622
@webpack_assets_compiler = webpack_assets_compiler
1723
@webpack_assets_checker = webpack_assets_checker
18-
@assets_have_been_compiled = false
24+
@webpack_process_checker = webpack_process_checker
1925
end
2026

2127
def call
22-
should_skip_compiling = assets_have_been_compiled || @webpack_assets_checker.up_to_date?
23-
webpack_assets_compiler.compile unless should_skip_compiling
24-
@assets_have_been_compiled = true
28+
loop do
29+
should_skip_compiling = self.class.has_been_run || @webpack_assets_checker.up_to_date?
30+
break if should_skip_compiling
31+
32+
if webpack_process_checker.running?
33+
sleep 1
34+
else
35+
webpack_assets_compiler.compile
36+
break
37+
end
38+
end
39+
40+
self.class.has_been_run = true
2541
end
2642
end
2743

2844
class WebpackAssetsCompiler
2945
def compile
3046
compile_type(:client)
31-
compile_type(:server) if ReactOnRails.configuration.server_bundle_js_file.present?
47+
compile_type(:server) if Utils.server_rendering_is_enabled?
3248
end
3349

3450
private
@@ -37,11 +53,45 @@ def compile_type(type)
3753
puts "\n\nBuilding Webpack #{type}-rendering assets..."
3854
build_output = `cd client && npm run build:#{type}`
3955

40-
if build_output =~ /error/i
41-
fail "Error in building assets!\n#{build_output}"
42-
end
56+
fail "Error in building assets!\n#{build_output}" unless Utils.last_process_completed_successfully?
57+
58+
puts "Webpack #{type}-rendering assets built. If you are frequently running\n"\
59+
"tests, you can run webpack in watch mode to speed up this process.\n"\
60+
"See the official documentation:\n"\
61+
"https://github.com/shakacode/react_on_rails/blob/master/docs/additional_reading/rspec_configuration.md\n\n"
62+
end
63+
end
64+
65+
class WebpackProcessChecker
66+
def running?
67+
client_running = check_running_for_type("client")
68+
return client_running unless Utils.server_rendering_is_enabled?
69+
70+
server_running = check_running_for_type("server")
71+
fail_if_only_running_for_one_type(client_running, server_running)
72+
73+
client_running && server_running
74+
end
75+
76+
private
77+
78+
# We only want to do this if server rendering is enabled.
79+
def fail_if_only_running_for_one_type(client_running, server_running)
80+
return unless client_running ^ server_running
81+
fail "\n\nError: detected webpack is not running for both types of assets:\n"\
82+
"***Webpack Client Process Running?: #{client_running}\n"\
83+
"***Webpack Server Process Running?: #{server_running}"
84+
end
85+
86+
def check_running_for_type(type)
87+
type = type.to_sym
88+
89+
response = `pgrep -fl 'bin/webpack\s(\\-w|\\-\\-watch)\s\\-\\-config\s.*#{type}.*\\.js'`
90+
is_running = Utils.last_process_completed_successfully?
91+
92+
puts "#{type} webpack process is running: #{response.ai}" if is_running
4393

44-
puts "Webpack #{type}-rendering assets built.\n\n"
94+
is_running
4595
end
4696
end
4797
end

lib/react_on_rails/utils.rb

+10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
1+
require "English"
2+
13
module ReactOnRails
24
module Utils
35
def self.object_to_boolean(value)
46
[true, "true", "yes", 1, "1", "t"].include?(value.class == String ? value.downcase : value)
57
end
8+
9+
def self.server_rendering_is_enabled?
10+
ReactOnRails.configuration.server_bundle_js_file.present?
11+
end
12+
13+
def self.last_process_completed_successfully?
14+
$CHILD_STATUS.exitstatus == 0
15+
end
616
end
717
end

lib/react_on_rails/webpack_assets_status_checker.rb

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require "rake"
2+
require "fileutils"
23

34
module ReactOnRails
45
class WebpackAssetsStatusChecker
@@ -10,18 +11,23 @@ def initialize(args = {})
1011
end
1112

1213
def up_to_date?
14+
create_compiled_dirs_if_missing
1315
return false unless assets_exist?
1416
all_compiled_assets.all? { |asset| FileUtils.uptodate?(asset, client_files) }
1517
end
1618

1719
private
1820

21+
def create_compiled_dirs_if_missing
22+
compiled_dirs.each { |dir| FileUtils.mkdir_p(dir) }
23+
end
24+
1925
def all_compiled_assets
20-
@all_compiled_assets ||= make_file_list(make_globs(compiled_dirs)).to_ary
26+
@all_compiled_assets = make_file_list(make_globs(compiled_dirs)).to_ary
2127
end
2228

2329
def client_files
24-
@client_files ||= make_file_list(make_globs(client_dir)).to_ary
30+
@client_files = make_file_list(make_globs(client_dir)).to_ary
2531
end
2632

2733
def make_globs(dirs)

spec/dummy/Gemfile.lock

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ PATH
44
react_on_rails (2.3.0)
55
connection_pool
66
execjs (~> 2.5)
7+
foreman
78
rails (>= 3.2)
89
rainbow (~> 2.1)
910

@@ -105,6 +106,8 @@ GEM
105106
erubis (2.7.0)
106107
execjs (2.6.0)
107108
ffi (1.9.10)
109+
foreman (0.78.0)
110+
thor (~> 0.19.1)
108111
generator_spec (0.9.3)
109112
activesupport (>= 3.0.0)
110113
railties (>= 3.0.0)

spec/dummy/Procfile.spec

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
client-static-assets: sh -c 'rm app/assets/javascripts/generated/* || true && cd client && npm run build:dev:client'
2+
server-static-assets: sh -c 'cd client && npm run build:dev:server'
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,62 @@
11
require_relative "simplecov_helper"
22
require_relative "spec_helper"
33

4-
class WebpackAssetsCompilerDouble
5-
attr_reader :times_ran
6-
7-
def initialize
8-
@times_ran = 0
9-
end
10-
11-
def compile
12-
@times_ran += 1
13-
end
14-
end
15-
164
module ReactOnRails
175
describe EnsureAssetsCompiled do
18-
let(:compiler) { WebpackAssetsCompilerDouble.new }
19-
let(:ensurer) { EnsureAssetsCompiled.new(checker, compiler) }
6+
let(:compiler) { double_assets_compiler }
7+
let(:ensurer) { EnsureAssetsCompiled.new(assets_checker, compiler, process_checker) }
8+
after { ensurer.class.has_been_run = false }
209

2110
context "when assets are not up to date" do
22-
let(:checker) { double_webpack_assets_checker(up_to_date: false) }
11+
let(:assets_checker) { double_assets_checker(up_to_date: false) }
12+
13+
context "and webpack process is running" do
14+
let(:process_checker) { double_process_checker(running: true) }
15+
16+
it "sleeps until assets are up to date" do
17+
expect(compiler).not_to receive(:compile)
18+
19+
thread = Thread.new { ensurer.call }
2320

24-
it "compiles the webpack bundles" do
25-
expect { ensurer.call }.to change { compiler.times_ran }.from(0).to(1)
21+
sleep 1
22+
allow(assets_checker).to receive(:up_to_date?).and_return(true)
23+
24+
thread.join
25+
26+
expect(ensurer.class.has_been_run).to eq(true)
27+
end
28+
end
29+
30+
context "and webpack process is NOT running" do
31+
let(:process_checker) { double_process_checker(running: false) }
32+
33+
it "compiles the webpack assets" do
34+
expect(compiler).to receive(:compile).once
35+
ensurer.call
36+
end
2637
end
2738
end
2839

2940
context "when assets are up to date" do
30-
let(:checker) { double_webpack_assets_checker(up_to_date: true) }
41+
let(:assets_checker) { double_assets_checker(up_to_date: true) }
42+
let(:process_checker) { double_process_checker(running: false) }
3143

32-
it "does not compile the webpack bundles if they exist and are up to date" do
33-
expect { ensurer.call }.not_to change { compiler.times_ran }
44+
it "does nothing" do
45+
expect(compiler).not_to receive(:compile)
46+
ensurer.call
3447
end
3548
end
3649

37-
def double_webpack_assets_checker(args = {})
50+
def double_process_checker(args = {})
51+
instance_double(WebpackProcessChecker, running?: args.fetch(:running))
52+
end
53+
54+
def double_assets_checker(args = {})
3855
instance_double(WebpackAssetsStatusChecker, up_to_date?: args.fetch(:up_to_date))
3956
end
57+
58+
def double_assets_compiler
59+
instance_double(WebpackAssetsCompiler, :compile)
60+
end
4061
end
4162
end

0 commit comments

Comments
 (0)