Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve EnsureAssetsCompiled logic to wait for webpack process to finish #253

Merged
merged 4 commits into from
Feb 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ Contributors: please follow the recommendations outlined at [keepachangelog.com]
## [Unreleased]
##### Added
- Added forman to gemspec in case new dev does not have it globally installed. [#248](https://github.com/shakacode/react_on_rails/pull/248)

##### Changed
- Changed the EnsureAssetsCompiled feature that ensures RSpec tests are run with the latest webpack bundles. Previously, webpack bundles would be rebuilt every test run, whether or not they actually needed to be. While one could around this by running webpack in the background to rebuild the static assets, doing so required users to name their webpack build script exactly correct as the technique relied on a `pgrep`.

Instead, the new functionality checks the mdate on each generated webpack bundle/asset against the mdate of every file inside of the `client` directory, which is all encapsulated by the standard library method `FileUtils.uptodate?` and is very performant. This eliminates the need to check for an existing webpack process because the webpack bundles will be up to date anyway if the latter is running.

Users need not make any changes to their code to benefit from the enhancement, assuming all of their generated files are output to `javascripts/generated`, `stylesheets/generated`, `fonts/generated`, or `images/generated`. [#251](https://github.com/shakacode/react_on_rails/pull/251)

##### Breaking Change
Renamed `ReactOnRails.configure_rspec_to_compile_assets` to `ReactOnRails::TestHelper.configure_rspec_to_compile_assets`

## [2.3.0] - 2016-02-01
##### Added
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ We're definitely not doing that. With react_on_rails, webpack is mainly generati
1. Add the following to your Gemfile and bundle install:

```ruby
gem "react_on_rails", "~> 2.0.0"
gem "react_on_rails", "~> 3"
```

2. See help for the generator:
Expand Down
16 changes: 10 additions & 6 deletions docs/additional_reading/rspec_configuration.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
# RSpec Configuration
Because you will probably want to run RSpec tests that rely on compiled webpack assets (typically, your integration/feature specs where `js: true`), you will want to ensure you don't accidentally run tests on missing or stale webpack assets. If you did use stale Webpack assets, you will get invalid test results as your tests do not use the very latest JavaScript code.

ReactOnRails provides a helper method called `configure_rspec_to_compile_assets`. Call this method from inside of the `RSpec.configure` block in your `spec/rails_helper.rb` file, passing the config as an argument.
ReactOnRails provides a helper method called `ReactOnRails::TestHelper.configure_rspec_to_compile_assets`. Call this method from inside of the `RSpec.configure` block in your `spec/rails_helper.rb` file, passing the config as an argument. See file [lib/react_on_rails/test_helper.rb](../../lib/react_on_rails/test_helper.rb) for more details. You can customize this to your particular needs by replacing any of the default components used by `ReactOnRails::TestHelper.configure_rspec_to_compile_assets`.

```ruby
RSpec.configure do |config|
# Next line will ensure that assets are built if webpack -w is not running
ReactOnRails.configure_rspec_to_compile_assets(config)
# Next line will ensure that assets are built if webpack -w is not running to build the bundles
ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
```

You can pass an RSpec metatag as an optional second parameter to this helper method if you want this helper to run on examples other than where `js: true` (default). The helper will compile webpack files at most once per test run.
You can pass an RSpec metatag as an optional second parameter to this helper method if you want this helper to run on examples other than where `js: true` (default). The helper will compile webpack files at most once per test run. The helper will not compile the webpack files unless they are out of date (stale).

If you do not want to be slowed down by re-compiling webpack assets from scratch every test run, you can call `npm run build:client` (and `npm run build:server` if doing server rendering) to have webpack recompile these files in the background, which will be much faster. The helper looks for these processes and will abort recompiling if it finds them to be running.
Please take note of the following:
- This utility assumes your build tasks for the static generated files are `npm run build:client` and `npm run build:server` and do not have the `--watch` option enabled.
- By default, the webpack processes look for the `app/assets/javascripts/generated` and `app/assets/stylesheets/generated` folders. If these folders are missing, are empty, or contain files with `mtime`s older than any of the files in your `client` folder, the helper will recompile your assets. You can override this inside of `config/initializers/react_on_rails.rb` by passing an array of filepaths (relative to the root of the app) to the `generated_assets_dirs` configuration option.

If you want to use a testing framework other than RSpec, we suggest you implement similar logic.
If you want to speed up the re-compiling process, you can call `npm run build:dev:client` (and `npm run build:dev:server` if doing server rendering) to have webpack run in "watch" mode and recompile these files in the background, which will be much faster when making incremental changes than compiling from scratch.

If you want to use a testing framework other than RSpec, please submit let us know on the changes you need to do and we'll update the docs.

![2016-01-27_02-36-43](https://cloud.githubusercontent.com/assets/1118459/12611951/7c56d070-c4a4-11e5-8a80-9615f99960d9.png)

Expand Down
14 changes: 6 additions & 8 deletions lib/react_on_rails.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@
require "react_on_rails/server_rendering_pool"
require "react_on_rails/engine"
require "react_on_rails/version_syntax_converter"
require "react_on_rails/ensure_assets_compiled"
require "react_on_rails/webpack_assets_status_checker"
require "react_on_rails/test_helper"
require "react_on_rails/git_utils"
require "react_on_rails/utils"

module ReactOnRails
def self.configure_rspec_to_compile_assets(config, metatag = :js)
config.before(:example, metatag) { EnsureAssetsCompiled.build.call }
end
end
require "react_on_rails/test_helper"
require "react_on_rails/test_helper/webpack_assets_compiler"
require "react_on_rails/test_helper/webpack_assets_status_checker"
require "react_on_rails/test_helper/webpack_process_checker"
require "react_on_rails/test_helper/ensure_assets_compiled"
14 changes: 10 additions & 4 deletions lib/react_on_rails/configuration.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@

module ReactOnRails
def self.configure
yield(configuration)
end

DEFAULT_GENERATED_ASSETS_DIRS = [
%w(app assets javascripts generated),
%w(app assets javascripts stylesheets generated)
].map { |dirs| File.join(*dirs) }.freeze

def self.configuration
@configuration ||= Configuration.new(
server_bundle_js_file: "app/assets/javascripts/generated/server.js",
generated_assets_dirs: DEFAULT_GENERATED_ASSETS_DIRS,
server_bundle_js_file: File.join(*%w(app assets javascripts generated server.js)),
prerender: false,
replay_console: true,
logging_on_server: true,
Expand All @@ -23,17 +28,18 @@ class Configuration
:trace, :development_mode,
:logging_on_server, :server_renderer_pool_size,
:server_renderer_timeout, :raise_on_prerender_error,
:skip_display_none
:skip_display_none, :generated_assets_dirs

def initialize(server_bundle_js_file: nil, prerender: nil, replay_console: nil,
trace: nil, development_mode: nil,
logging_on_server: nil, server_renderer_pool_size: nil,
server_renderer_timeout: nil, raise_on_prerender_error: nil,
skip_display_none: nil)
skip_display_none: nil, generated_assets_dirs: DEFAULT_GENERATED_ASSETS_DIRS)
self.server_bundle_js_file = if File.exist?(server_bundle_js_file)
server_bundle_js_file
end

self.generated_assets_dirs = generated_assets_dirs
self.prerender = prerender
self.replay_console = replay_console
self.logging_on_server = logging_on_server
Expand Down
47 changes: 0 additions & 47 deletions lib/react_on_rails/ensure_assets_compiled.rb

This file was deleted.

67 changes: 67 additions & 0 deletions lib/react_on_rails/test_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
module ReactOnRails
module TestHelper
# Because you will probably want to run RSpec tests that rely on compiled webpack assets
# (typically, your integration/feature specs where `js: true`), you will want to ensure you
# don't accidentally run tests on missing or stale webpack assets. If you did use stale
# Webpack assets, you will get invalid test results as your tests do not use the very latest
# JavaScript code.
#
# Call this method from inside of the `RSpec.configure` block in your `spec/rails_helper.rb`
# file, passing the config as an argument. You can customize this to your particular needs by
# replacing any of the default components.
#
# RSpec.configure do |config|
# ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config)
#
# You can pass an RSpec metatag as an optional second parameter to this helper method
# if you want this helper to run on examples other than where `js: true` (default). The helper
# will compile webpack files at most once per test run.
#
# If you do not want to be slowed down by re-compiling webpack assets from scratch every test
# run, you can call `npm run build:client` (and `npm run build:server` if doing server
# rendering) to have webpack recompile these files in the background, which will be *much*
# faster. The helper looks for these processes and will abort recompiling if it finds them
# to be running.
#
# See docs/additional_reading/rspec_configuration.md for more info
def self.configure_rspec_to_compile_assets(config, metatag = :js)
config.before(:example, metatag) { ReactOnRails::TestHelper.ensure_assets_compiled }
end

# Main entry point to ensuring assets are compiled. See `configure_rspec_to_compile_assets` for
# an example of usage.
#
# Typical usage passes all params as nil defaults.
# webpack_assets_status_checker: provide: `up_to_date?`, `whats_not_up_to_date`, `client_dir`
# defaults to ReactOnRails::TestHelper::WebpackAssetsStatusChecker
# webpack_process_checker: provide one method: `def running?`
# defaults to ReactOnRails::TestHelper::WebpackProcessChecker
# webpack_assets_compiler: provide one method: `def compile`
# defaults to ReactOnRails::TestHelper::WebpackAssetsCompiler
# client_dir and compiled_dirs are passed into the default webpack_assets_status_checker if you
# don't provide one.
def self.ensure_assets_compiled(webpack_assets_status_checker: nil,
webpack_assets_compiler: nil,
webpack_process_checker: nil,
client_dir: nil,
compiled_dirs: nil)

if webpack_assets_status_checker.nil?
client_dir ||= Rails.root.join("client")
compiled_dirs ||= ReactOnRails.configuration.generated_assets_dirs
webpack_assets_status_checker ||=
WebpackAssetsStatusChecker.new(client_dir: client_dir,
compiled_dirs: compiled_dirs)
end

webpack_assets_compiler ||= WebpackAssetsCompiler.new
webpack_process_checker ||= WebpackProcessChecker.new

ReactOnRails::TestHelper::EnsureAssetsCompiled.new(
webpack_assets_status_checker: webpack_assets_status_checker,
webpack_assets_compiler: webpack_assets_compiler,
webpack_process_checker: webpack_process_checker
).call
end
end
end
73 changes: 73 additions & 0 deletions lib/react_on_rails/test_helper/ensure_assets_compiled.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
module ReactOnRails
module TestHelper
class EnsureAssetsCompiled
class << self
attr_accessor :has_been_run
@has_been_run = false
end

attr_reader :webpack_assets_status_checker,
:webpack_assets_compiler,
:webpack_process_checker

MAX_TIME_TO_WAIT = 5

def initialize(webpack_assets_status_checker: nil,
webpack_assets_compiler: nil,
webpack_process_checker: nil)
@webpack_assets_status_checker = webpack_assets_status_checker
@webpack_assets_compiler = webpack_assets_compiler
@webpack_process_checker = webpack_process_checker
end

def call
return if self.class.has_been_run

loop_count = 0
loop do
break if webpack_assets_status_checker.up_to_date?

puts_first_iteration_message(loop_count)

if webpack_process_checker.running? && loop_count < MAX_TIME_TO_WAIT
loop_count += 1
sleep 1
else
puts_max_iterations_message(loop_count)

webpack_assets_compiler.compile
puts
break
end
end

self.class.has_been_run = true
end

def puts_first_iteration_message(loop_count)
return unless loop_count == 0

puts "\n\nReact on Rails is ensuring your JavaScript generated files are up to date!"
end

def puts_max_iterations_message(loop_count)
if loop_count == MAX_TIME_TO_WAIT
stale_files = webpack_assets_status_checker.whats_not_up_to_date.join("\n")

puts <<-MSG

Even though we detected the webpack watch processes are running, we found files modified that are
not causing a rebuild of your generated files:

#{stale_files}

One possibility is that you modified a file in your directory that is not a dependency of
your webpack files: #{webpack_assets_status_checker.client_dir}

To be sure, we will now rebuild your generated files.
MSG
end
end
end
end
end
33 changes: 33 additions & 0 deletions lib/react_on_rails/test_helper/webpack_assets_compiler.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# You can replace this implementation with your own for use by the
# ReactOnRails::TestHelper.ensure_assets_compiled helper
module ReactOnRails
module TestHelper
class WebpackAssetsCompiler
def compile
compile_type(:client)
compile_type(:server) if Utils.server_rendering_is_enabled?
end

private

def compile_type(type)
unless @printed_msg
puts <<-MSG
If you are frequently running tests, you can run webpack in watch mode to speed up this process.
See the official documentation:
https://github.com/shakacode/react_on_rails/blob/master/docs/additional_reading/rspec_configuration.md
MSG
@printed_msg = true
end

puts "\nBuilding Webpack #{type}-rendering assets..."

build_output = `cd client && npm run build:#{type}`

fail "Error in building assets!\n#{build_output}" unless Utils.last_process_completed_successfully?

puts "Completed building Webpack #{type}-rendering assets."
end
end
end
end
Loading