diff --git a/lib/react_on_rails/ensure_assets_compiled.rb b/lib/react_on_rails/ensure_assets_compiled.rb index 191c8930b2..6e8c75c8c5 100644 --- a/lib/react_on_rails/ensure_assets_compiled.rb +++ b/lib/react_on_rails/ensure_assets_compiled.rb @@ -5,22 +5,36 @@ class EnsureAssetsCompiled def self.build client_dir = Rails.root.join("client") compiled_dirs = COMPILED_DIR_NAMES.map { |dir| Rails.root.join("app", "assets", dir, "generated") } - checker = WebpackAssetsStatusChecker.new(client_dir: client_dir, compiled_dirs: compiled_dirs) + + assets_checker = WebpackAssetsStatusChecker.new(client_dir: client_dir, compiled_dirs: compiled_dirs) + process_checker = WebpackProcessChecker.new compiler = WebpackAssetsCompiler.new - new(checker, compiler) + + new(assets_checker, compiler, process_checker) end - attr_reader :webpack_assets_checker, :webpack_assets_compiler, :assets_have_been_compiled + attr_reader :webpack_assets_checker, :webpack_assets_compiler, :webpack_process_checker, :assets_have_been_compiled - def initialize(webpack_assets_checker, webpack_assets_compiler) + def initialize(webpack_assets_checker, webpack_assets_compiler, webpack_process_checker) @webpack_assets_compiler = webpack_assets_compiler @webpack_assets_checker = webpack_assets_checker + @webpack_process_checker = webpack_process_checker @assets_have_been_compiled = false end def call - should_skip_compiling = assets_have_been_compiled || @webpack_assets_checker.up_to_date? - webpack_assets_compiler.compile unless should_skip_compiling + loop do + should_skip_compiling = assets_have_been_compiled || @webpack_assets_checker.up_to_date? + break if should_skip_compiling + + if webpack_process_checker.running? + sleep 1 + else + webpack_assets_compiler.compile + break + end + end + @assets_have_been_compiled = true end end @@ -28,7 +42,7 @@ def call class WebpackAssetsCompiler def compile compile_type(:client) - compile_type(:server) if ReactOnRails.configuration.server_bundle_js_file.present? + compile_type(:server) if Utils.server_rendering_is_enabled? end private @@ -37,11 +51,43 @@ def compile_type(type) puts "\n\nBuilding Webpack #{type}-rendering assets..." build_output = `cd client && npm run build:#{type}` - if build_output =~ /error/i - fail "Error in building assets!\n#{build_output}" - end + fail "Error in building assets!\n#{build_output}" unless Utils.last_process_completed_successfully? + + puts "Webpack #{type}-rendering assets built. If you are frequently running\n"\ + "tests, you can run webpack in watch mode to speed up this process.\n\n" + end + end + + class WebpackProcessChecker + def running? + client_running = check_running_for_type("client") + return client_running unless Utils.server_rendering_is_enabled? + + server_running = check_running_for_type("server") + + fail_if_only_running_for_one_type(client_running, server_running) + + client_running && server_running + end + + private + + def fail_if_only_running_for_one_type(client_running, server_running) + return unless client_running ^ server_running + fail "\n\nError: detected webpack is not running for both types of assets:\n"\ + "***Webpack Client Process Running?: #{client_running}\n"\ + "***Webpack Server Process Running?: #{server_running}" + end + + def check_running_for_type(type) + type = type.to_sym + + response = `pgrep -fl 'bin/webpack\s(\\-w|\\-\\-watch)\s\\-\\-config\s.*#{type}.*\\.js'` + is_running = Utils.last_process_completed_successfully? + + puts "#{type} webpack process is running: #{response.ai}" if is_running - puts "Webpack #{type}-rendering assets built.\n\n" + is_running end end end diff --git a/lib/react_on_rails/utils.rb b/lib/react_on_rails/utils.rb index 4d342f3f78..6f5a43b835 100644 --- a/lib/react_on_rails/utils.rb +++ b/lib/react_on_rails/utils.rb @@ -1,7 +1,17 @@ +require "english" + module ReactOnRails module Utils def self.object_to_boolean(value) [true, "true", "yes", 1, "1", "t"].include?(value.class == String ? value.downcase : value) end + + def self.server_rendering_is_enabled? + ReactOnRails.configuration.server_bundle_js_file.present? + end + + def self.last_process_completed_successfully? + $CHILD_STATUS.exitstatus == 0 + end end end diff --git a/lib/react_on_rails/webpack_assets_status_checker.rb b/lib/react_on_rails/webpack_assets_status_checker.rb index 6815f1ec05..e7159dd2aa 100644 --- a/lib/react_on_rails/webpack_assets_status_checker.rb +++ b/lib/react_on_rails/webpack_assets_status_checker.rb @@ -17,11 +17,11 @@ def up_to_date? private def all_compiled_assets - @all_compiled_assets ||= make_file_list(make_globs(compiled_dirs)).to_ary + @all_compiled_assets = make_file_list(make_globs(compiled_dirs)).to_ary end def client_files - @client_files ||= make_file_list(make_globs(client_dir)).to_ary + @client_files = make_file_list(make_globs(client_dir)).to_ary end def make_globs(dirs) diff --git a/spec/dummy/Gemfile.lock b/spec/dummy/Gemfile.lock index 98eeac94e0..d2abc5551a 100644 --- a/spec/dummy/Gemfile.lock +++ b/spec/dummy/Gemfile.lock @@ -4,6 +4,7 @@ PATH react_on_rails (2.3.0) connection_pool execjs (~> 2.5) + foreman rails (>= 3.2) rainbow (~> 2.1) @@ -105,6 +106,8 @@ GEM erubis (2.7.0) execjs (2.6.0) ffi (1.9.10) + foreman (0.78.0) + thor (~> 0.19.1) generator_spec (0.9.3) activesupport (>= 3.0.0) railties (>= 3.0.0) diff --git a/spec/dummy/Procfile.spec b/spec/dummy/Procfile.spec new file mode 100644 index 0000000000..2c2700a290 --- /dev/null +++ b/spec/dummy/Procfile.spec @@ -0,0 +1,2 @@ +client-static-assets: sh -c 'rm app/assets/javascripts/generated/* || true && cd client && npm run build:dev:client' +server-static-assets: sh -c 'cd client && npm run build:dev:server' diff --git a/spec/react_on_rails/ensure_assets_compiled_spec.rb b/spec/react_on_rails/ensure_assets_compiled_spec.rb index 095e607307..a7cdb96f9b 100644 --- a/spec/react_on_rails/ensure_assets_compiled_spec.rb +++ b/spec/react_on_rails/ensure_assets_compiled_spec.rb @@ -1,41 +1,61 @@ require_relative "simplecov_helper" require_relative "spec_helper" -class WebpackAssetsCompilerDouble - attr_reader :times_ran - - def initialize - @times_ran = 0 - end - - def compile - @times_ran += 1 - end -end - module ReactOnRails describe EnsureAssetsCompiled do - let(:compiler) { WebpackAssetsCompilerDouble.new } - let(:ensurer) { EnsureAssetsCompiled.new(checker, compiler) } + let(:compiler) { double_assets_compiler } + let(:ensurer) { EnsureAssetsCompiled.new(assets_checker, compiler, process_checker) } context "when assets are not up to date" do - let(:checker) { double_webpack_assets_checker(up_to_date: false) } + let(:assets_checker) { double_assets_checker(up_to_date: false) } + + context "and webpack process is running" do + let(:process_checker) { double_process_checker(running: true) } + + it "sleeps until assets are up to date" do + expect(compiler).not_to receive(:compile) + + thread = Thread.new { ensurer.call } - it "compiles the webpack bundles" do - expect { ensurer.call }.to change { compiler.times_ran }.from(0).to(1) + sleep 1 + allow(assets_checker).to receive(:up_to_date?).and_return(true) + + thread.join + + expect(ensurer.assets_have_been_compiled).to eq(true) + end + end + + context "and webpack process is NOT running" do + let(:process_checker) { double_process_checker(running: false) } + + it "compiles the webpack assets" do + expect(compiler).to receive(:compile).once + ensurer.call + end end end context "when assets are up to date" do - let(:checker) { double_webpack_assets_checker(up_to_date: true) } + let(:assets_checker) { double_assets_checker(up_to_date: true) } + let(:process_checker) { double_process_checker(running: false) } - it "does not compile the webpack bundles if they exist and are up to date" do - expect { ensurer.call }.not_to change { compiler.times_ran } + it "does nothing" do + expect(compiler).not_to receive(:compile) + ensurer.call end end - def double_webpack_assets_checker(args = {}) + def double_process_checker(args = {}) + instance_double(WebpackProcessChecker, running?: args.fetch(:running)) + end + + def double_assets_checker(args = {}) instance_double(WebpackAssetsStatusChecker, up_to_date?: args.fetch(:up_to_date)) end + + def double_assets_compiler + instance_double(WebpackAssetsCompiler, :compile) + end end end