react_on_rails 6.0.5 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintrc +2 -3
  3. data/.gitignore +1 -1
  4. data/.travis.yml +15 -13
  5. data/CHANGELOG.md +27 -15
  6. data/{docs/contributor-info/contributing.md → CONTRIBUTING.md} +24 -1
  7. data/Gemfile +1 -1
  8. data/KUDOS.md +3 -0
  9. data/NEWS.md +3 -0
  10. data/PROJECTS.md +1 -1
  11. data/README.md +143 -202
  12. data/app/helpers/react_on_rails_helper.rb +29 -17
  13. data/docs/additional-reading/node-server-rendering.md +127 -7
  14. data/docs/additional-reading/rails-assets.md +74 -10
  15. data/docs/additional-reading/react-router.md +1 -1
  16. data/docs/additional-reading/server-rendering-tips.md +3 -0
  17. data/docs/api/javascript-api.md +23 -0
  18. data/docs/contributor-info/releasing.md +1 -1
  19. data/docs/misc/doctrine.md +6 -6
  20. data/docs/tutorial.md +74 -93
  21. data/lib/generators/USAGE +5 -1
  22. data/lib/generators/react_on_rails/base_generator.rb +2 -2
  23. data/lib/generators/react_on_rails/install_generator.rb +8 -0
  24. data/lib/generators/react_on_rails/node_generator.rb +22 -0
  25. data/lib/generators/react_on_rails/templates/base/base/client/REACT_ON_RAILS_CLIENT_README.md +1 -1
  26. data/lib/generators/react_on_rails/templates/base/base/client/webpack.config.js +3 -2
  27. data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +7 -3
  28. data/lib/generators/react_on_rails/templates/node/base/client/node/package.json +10 -0
  29. data/lib/generators/react_on_rails/templates/node/base/client/node/server.js +87 -0
  30. data/lib/react_on_rails/assets_precompile.rb +112 -0
  31. data/lib/react_on_rails/configuration.rb +1 -1
  32. data/lib/react_on_rails/server_rendering_pool.rb +1 -0
  33. data/lib/react_on_rails/test_helper/ensure_assets_compiled.rb +1 -1
  34. data/lib/react_on_rails/utils.rb +1 -0
  35. data/lib/react_on_rails/version.rb +1 -1
  36. data/lib/react_on_rails/version_checker.rb +3 -2
  37. data/lib/tasks/assets.rake +10 -73
  38. data/package.json +9 -7
  39. metadata +7 -5
  40. data/Dockerfile_tests +0 -12
  41. data/docker-compose.yml +0 -11
@@ -9,6 +9,10 @@ can pass the redux option if you'd like to have redux setup for you automaticall
9
9
  to integrate the Redux state container framework. The necessary node modules
10
10
  will be automatically included for you.
11
11
 
12
+ * Node
13
+
14
+ Passing the --node generator option sets up the necessary files for node to render the react_components.
15
+
12
16
  *******************************************************************************
13
17
 
14
18
  After running the generator, you will want to:
@@ -21,4 +25,4 @@ Then you may run
21
25
 
22
26
  More Details:
23
27
 
24
- `https://github.com/shakacode/react_on_rails#generator`
28
+ `https://github.com/shakacode/react_on_rails/blob/master/docs/basics/generator.md`
@@ -23,7 +23,7 @@ module ReactOnRails
23
23
  def update_git_ignore
24
24
  data = <<-DATA.strip_heredoc
25
25
  # React on Rails
26
- npm-debug.log
26
+ npm-debug.log*
27
27
  node_modules
28
28
 
29
29
  # Generated js bundles
@@ -82,7 +82,7 @@ module ReactOnRails
82
82
  end
83
83
 
84
84
  def add_base_gems_to_gemfile
85
- append_to_file("Gemfile", "\ngem 'therubyracer', platforms: :ruby\n")
85
+ append_to_file("Gemfile", "\ngem 'mini_racer', platforms: :ruby\n")
86
86
  end
87
87
 
88
88
  ASSETS_RB_APPEND = <<-DATA.strip_heredoc
@@ -17,6 +17,13 @@ module ReactOnRails
17
17
  desc: "Install Redux gems and Redux version of Hello World Example. Default: false",
18
18
  aliases: "-R"
19
19
 
20
+ # --redux
21
+ class_option :node,
22
+ type: :boolean,
23
+ default: false,
24
+ desc: "Sets up node as a server rendering option. Default: false",
25
+ aliases: "-N"
26
+
20
27
  # --ignore-warnings
21
28
  class_option :ignore_warnings,
22
29
  type: :boolean,
@@ -46,6 +53,7 @@ module ReactOnRails
46
53
  invoke "react_on_rails:base"
47
54
  invoke "react_on_rails:react_no_redux" unless options.redux?
48
55
  invoke "react_on_rails:react_with_redux" if options.redux?
56
+ invoke "react_on_rails:node" if options.node?
49
57
  end
50
58
 
51
59
  # NOTE: other requirements for existing files such as .gitignore or application.
@@ -0,0 +1,22 @@
1
+ require "rails/generators"
2
+
3
+ module ReactOnRails
4
+ module Generators
5
+ class NodeGenerator < Rails::Generators::Base
6
+ Rails::Generators.hide_namespace(namespace)
7
+ source_root(File.expand_path("../templates", __FILE__))
8
+
9
+ def create_node_directory
10
+ empty_directory("client/node")
11
+ end
12
+
13
+ def copy_base_redux_files
14
+ base_path = "node/base/"
15
+ %w(client/node/server.js
16
+ client/node/package.json).each do |file|
17
+ copy_file(base_path + file, file)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -5,5 +5,5 @@ See documentation [at github.com/shakacode/react_on_rails](https://github.com/sh
5
5
  If you need additional help, please consider:
6
6
 
7
7
  * [Our ShakaCode Forum for React on Rails](https://forum.shakacode.com/c/rails/reactonrails).
8
- * Joining our Slack discussion room by [email us a bit about you and your project](mailto:contact@shakacode.com).
8
+ * Joining our Slack discussion room by [emailing us a bit about you and your project](mailto:contact@shakacode.com).
9
9
  * [Hiring us](https://forum.shakacode.com/c/rails/reactonrails) for coaching and custom web application development for your project.
@@ -4,7 +4,7 @@ const path = require('path');
4
4
  const devBuild = process.env.NODE_ENV !== 'production';
5
5
  const nodeEnv = devBuild ? 'development' : 'production';
6
6
 
7
- config = {
7
+ const config = {
8
8
  entry: [
9
9
  'es5-shim/es5-shim',
10
10
  'es5-shim/es5-sham',
@@ -38,7 +38,8 @@ config = {
38
38
  loader: 'imports?shim=es5-shim/es5-shim&sham=es5-shim/es5-sham',
39
39
  },
40
40
  {
41
- test: /\.jsx?$/, loader: 'babel-loader',
41
+ test: /\.jsx?$/,
42
+ loader: 'babel-loader',
42
43
  exclude: /node_modules/,
43
44
  },
44
45
  ],
@@ -52,7 +52,7 @@ ReactOnRails.configure do |config|
52
52
 
53
53
  # Server rendering only (not for render_component helper)
54
54
  # You can configure your pool of JS virtual machines and specify where it should load code:
55
- # On MRI, use `therubyracer` for the best performance
55
+ # On MRI, use `mini_racer` for the best performance
56
56
  # (see [discussion](https://github.com/reactjs/react-rails/pull/290))
57
57
  # On MRI, you'll get a deadlock with `pool_size` > 1
58
58
  # If you're using JRuby, you can increase `pool_size` to have real multi-threaded rendering.
@@ -67,11 +67,15 @@ ReactOnRails.configure do |config|
67
67
  config.skip_display_none = false
68
68
 
69
69
  # The server render method - either ExecJS or NodeJS
70
+ <%- if options.node? -%>
71
+ config.server_render_method = "NodeJS"
72
+ <%- else -%>
70
73
  config.server_render_method = "ExecJS"
74
+ <%- end -%>
71
75
 
72
76
  # Client js uses assets not digested by rails.
73
- # For any asset matching this regex, non-digested symlink will be created
77
+ # For any asset matching this regex, non-digested symlink will be created (what webpack's css wants)
74
78
  # To disable symlinks set this parameter to nil.
75
- config.symlink_non_digested_assets_regex = /\.(png|jpg|jpeg|gif|tiff|woff|ttf|eot|svg)/
79
+ config.symlink_non_digested_assets_regex = /\.(png|jpg|jpeg|gif|tiff|woff|ttf|eot|svg|map)/
76
80
 
77
81
  end
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "react_on_rails_node",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "start": "node ./server.js -s webpack-bundle.js"
7
+ },
8
+ "dependencies": {
9
+ }
10
+ }
@@ -0,0 +1,87 @@
1
+ const net = require('net');
2
+ const fs = require('fs');
3
+
4
+ const bundlePath = '../../app/assets/webpack/';
5
+ let bundleFileName = 'webpack-bundle.js';
6
+
7
+ let currentArg;
8
+
9
+ function Handler() {
10
+ this.queue = [];
11
+ this.initialized = false;
12
+ }
13
+
14
+ Handler.prototype.handle = (connection) => {
15
+ const callback = () => {
16
+ connection.setEncoding('utf8');
17
+ connection.on('data', (data) => {
18
+ console.log(`Processing request: ${data}`);
19
+
20
+ // eslint-disable-next-line no-eval
21
+ const result = eval(data);
22
+ connection.write(result);
23
+ });
24
+ };
25
+
26
+ if (this.initialized) {
27
+ callback();
28
+ } else {
29
+ this.queue.push(callback);
30
+ }
31
+ };
32
+
33
+ Handler.prototype.initialize = () => {
34
+ console.log(`Processing ${this.queue.length} pending requests`);
35
+ let callback;
36
+
37
+ // eslint-disable-next-line no-cond-assign
38
+ while (callback = this.queue.pop()) {
39
+ callback();
40
+ }
41
+
42
+ this.initialized = true;
43
+ };
44
+
45
+ const handler = new Handler();
46
+
47
+ process.argv.forEach((val) => {
48
+ if (val[0] === '-') {
49
+ currentArg = val.slice(1);
50
+ return;
51
+ }
52
+
53
+ if (currentArg === 's') {
54
+ bundleFileName = val;
55
+ }
56
+ });
57
+
58
+ try {
59
+ fs.mkdirSync(bundlePath);
60
+ } catch (e) {
61
+ if (e.code !== 'EEXIST') throw e;
62
+ }
63
+
64
+ fs.watchFile(bundlePath + bundleFileName, (curr) => {
65
+ if (curr && curr.blocks && curr.blocks > 0) {
66
+ if (handler.initialized) {
67
+ console.log('Reloading server bundle must be implemented by restarting the node process!');
68
+ return;
69
+ }
70
+
71
+ // eslint-disable-next-line global-require
72
+ require(bundlePath + bundleFileName);
73
+ console.log(`Loaded server bundle: ${bundlePath + bundleFileName}`);
74
+ handler.initialize();
75
+ }
76
+ });
77
+
78
+ const unixServer = net.createServer((connection) => {
79
+ handler.handle(connection);
80
+ });
81
+
82
+ unixServer.listen('node.sock');
83
+
84
+ process.on('SIGINT', () => {
85
+ unixServer.close();
86
+ process.exit();
87
+ });
@@ -0,0 +1,112 @@
1
+ module ReactOnRails
2
+ class AssetsPrecompile
3
+ # Used by the rake task
4
+ def default_asset_path
5
+ dir = File.join(Rails.configuration.paths["public"].first,
6
+ Rails.configuration.assets.prefix)
7
+ Pathname.new(dir)
8
+ end
9
+
10
+ def initialize(assets_path: nil,
11
+ symlink_non_digested_assets_regex: nil,
12
+ generated_assets_dir: nil)
13
+ @assets_path = assets_path.presence || default_asset_path
14
+ @symlink_non_digested_assets_regex = symlink_non_digested_assets_regex.presence ||
15
+ ReactOnRails.configuration.symlink_non_digested_assets_regex
16
+ @generated_assets_dir = generated_assets_dir.presence || ReactOnRails.configuration.generated_assets_dir
17
+ end
18
+
19
+ # target and symlink are relative to the assets directory
20
+ def symlink_file(target, symlink)
21
+ target_path = @assets_path.join(target)
22
+ symlink_path = @assets_path.join(symlink)
23
+ target_exists = File.exist?(target_path)
24
+
25
+ # File.exist?(symlink_path) will check the file the sym is pointing to is existing
26
+ # File.lstat(symlink_path).symlink? confirms that this is a symlink
27
+ symlink_already_there_and_valid = File.exist?(symlink_path) &&
28
+ File.lstat(symlink_path).symlink?
29
+ if symlink_already_there_and_valid
30
+ puts "React On Rails: Digested #{symlink} already exists indicating #{target} did not change."
31
+ elsif target_exists
32
+ if File.exist?(symlink_path) && File.lstat(symlink_path).symlink?
33
+ puts "React On Rails: Removing invalid symlink #{symlink_path}"
34
+ `cd #{@assets_path} && rm #{symlink}`
35
+ end
36
+ # Might be like:
37
+ # "images/5cf5db49df178f9357603f945752a1ef.png":
38
+ # "images/5cf5db49df178f9357603f945752a1ef-033650e1d6193b70d59bb60e773f47b6d9aefdd56abc7cc.png"
39
+ # need to cd to directory and then symlink
40
+ target_sub_path, _divider, target_filename = target.rpartition("/")
41
+ _symlink_sub_path, _divider, symlink_filename = symlink.rpartition("/")
42
+ puts "React On Rails: Symlinking \"#{target}\" to \"#{symlink}\""
43
+ dest_path = File.join(@assets_path, target_sub_path)
44
+ FileUtils.chdir(dest_path) do
45
+ File.symlink(target_filename, symlink_filename)
46
+ end
47
+ end
48
+ end
49
+
50
+ def symlink_non_digested_assets
51
+ # digest ==> means that the file has a unique sha so the browser will load a new copy.
52
+ # Webpack's CSS extract-text-plugin copies digested asset files over to directory where we put
53
+ # we deploy the webpack compiled JS file. Since Rails will deploy the image files in this
54
+ # directory with a digest, then the files are essentially "double-digested" and the CSS
55
+ # references from webpack's CSS would be invalid. The fix is to symlink the double-digested
56
+ # file back to the original digested name, and make a similar symlink for the gz version.
57
+ if @symlink_non_digested_assets_regex
58
+ manifest_glob = Dir.glob(@assets_path.join(".sprockets-manifest-*.json")) +
59
+ Dir.glob(@assets_path.join("manifest-*.json"))
60
+ if manifest_glob.empty?
61
+ puts "Warning: React On Rails: expected to find .sprockets-manifest-*.json or manifest-*.json "\
62
+ "at #{@assets_path}, but found none. Canceling symlinking tasks."
63
+ return -1
64
+ end
65
+ manifest_path = manifest_glob.first
66
+ manifest_data = JSON.load(File.new(manifest_path))
67
+
68
+ # We realize that we're copying other Rails assets that match the regexp, but this just
69
+ # means that we'd be exposing the original, undigested names.
70
+ manifest_data["assets"].each do |original_filename, rails_digested_filename|
71
+ # TODO: we should remove any original_filename that is NOT in the webpack deploy folder.
72
+ next unless original_filename =~ @symlink_non_digested_assets_regex
73
+ # We're symlinking from the digested filename back to the original filename which has
74
+ # already been symlinked by Webpack
75
+ symlink_file(rails_digested_filename, original_filename)
76
+
77
+ # We want the gz ones as well
78
+ symlink_file("#{rails_digested_filename}.gz", "#{original_filename}.gz")
79
+ end
80
+ end
81
+ end
82
+
83
+ def delete_broken_symlinks
84
+ Dir.glob(@assets_path.join("*")).each do |filename|
85
+ next unless File.lstat(filename).symlink?
86
+ begin
87
+ target = File.readlink(filename)
88
+ rescue
89
+ puts "React on Rails: Warning: your platform doesn't support File::readlink method." /
90
+ "Skipping broken link check."
91
+ break
92
+ end
93
+ path = Pathname.new(File.dirname(filename))
94
+ target_path = path.join(target)
95
+ unless File.exist?(target_path)
96
+ puts "React on Rails: Deleting broken link: #{filename}"
97
+ File.delete(filename)
98
+ end
99
+ end
100
+ end
101
+
102
+ def clobber
103
+ dir = Rails.root.join(@generated_assets_dir)
104
+ if dir.present? && File.directory?(dir)
105
+ puts "Deleting files in directory #{dir}"
106
+ FileUtils.rm_r(Dir.glob(Rails.root.join("#{@generated_assets_dir}/*")))
107
+ else
108
+ puts "Could not find generated_assets_dir #{dir} defined in react_on_rails initializer: "
109
+ end
110
+ end
111
+ end
112
+ end
@@ -57,7 +57,7 @@ module ReactOnRails
57
57
  webpack_generated_files: [],
58
58
  rendering_extension: nil,
59
59
  server_render_method: "",
60
- symlink_non_digested_assets_regex: /\.(png|jpg|jpeg|gif|tiff|woff|ttf|eot|svg)/,
60
+ symlink_non_digested_assets_regex: /\.(png|jpg|jpeg|gif|tiff|woff|ttf|eot|svg|map)/,
61
61
  npm_build_test_command: "",
62
62
  npm_build_production_command: ""
63
63
  )
@@ -16,6 +16,7 @@ module ReactOnRails
16
16
  end
17
17
  end
18
18
 
19
+ # rubocop:disable Style/MethodMissing
19
20
  def method_missing(sym, *args, &block)
20
21
  pool.send sym, *args, &block
21
22
  end
@@ -45,7 +45,7 @@ Detected are the following stale generated files:
45
45
  #{stale_files.join("\n")}
46
46
 
47
47
  React on Rails will ensure your JavaScript generated files are up to date, using your
48
- /client level package.json `build:test` command.
48
+ /client level package.json `#{ReactOnRails.configuration.npm_build_test_command}` command.
49
49
 
50
50
  MSG
51
51
  end
@@ -11,6 +11,7 @@ module ReactOnRails
11
11
  end
12
12
 
13
13
  def self.last_process_completed_successfully?
14
+ # rubocop:disable Style/NumericPredicate
14
15
  $CHILD_STATUS.exitstatus == 0
15
16
  end
16
17
 
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ReactOnRails
3
- VERSION = "6.0.5".freeze
3
+ VERSION = "6.1.0".freeze
4
4
  end
@@ -3,6 +3,7 @@ module ReactOnRails
3
3
  # against each otherat runtime.
4
4
  class VersionChecker
5
5
  attr_reader :node_package_version, :logger
6
+ MAJOR_VERSION_REGEX = /(\d+)\.?/
6
7
 
7
8
  def self.build
8
9
  new(NodePackageVersion.build, Rails.logger)
@@ -38,7 +39,7 @@ module ReactOnRails
38
39
  end
39
40
 
40
41
  def gem_major_version
41
- gem_version.match(/(\d+)\./)[1]
42
+ gem_version.match(MAJOR_VERSION_REGEX)[1]
42
43
  end
43
44
 
44
45
  class NodePackageVersion
@@ -66,7 +67,7 @@ module ReactOnRails
66
67
 
67
68
  def major
68
69
  return if relative_path?
69
- raw.match(/(\d+)\./)[1]
70
+ raw.match(MAJOR_VERSION_REGEX)[1]
70
71
  end
71
72
 
72
73
  private
@@ -1,72 +1,15 @@
1
- module ReactOnRails
2
- class << self
3
- def assets_path
4
- dir = File.join(Rails.configuration.paths['public'].first,
5
- Rails.configuration.assets.prefix)
6
- Pathname.new(dir)
7
- end
8
-
9
- def symlink_file(target, symlink)
10
- target_path = ReactOnRails::assets_path.join(target)
11
- symlink_path = ReactOnRails::assets_path.join(symlink)
12
- if not File.exist?(symlink_path) or File.lstat(symlink_path).symlink?
13
- if File.exist?(target_path)
14
- puts "React On Rails: Symlinking #{target_path} to #{symlink_path}"
15
- `cd #{ReactOnRails::assets_path} && ln -s #{target} #{symlink}`
16
- end
17
- else
18
- puts "React On Rails: File #{symlink_path} already exists. Failed to symlink #{target_path}"
19
- end
20
- end
21
- end
22
- end
1
+ require "react_on_rails/assets_precompile"
23
2
 
24
3
  namespace :react_on_rails do
25
4
  namespace :assets do
26
5
  desc "Creates non-digested symlinks for the assets in the public asset dir"
27
6
  task symlink_non_digested_assets: :"assets:environment" do
28
- if ReactOnRails.configuration.symlink_non_digested_assets_regex
29
- manifest_glob = Dir.glob(ReactOnRails::assets_path.join(".sprockets-manifest-*.json")) +
30
- Dir.glob(ReactOnRails::assets_path.join("manifest-*.json"))
31
- if manifest_glob.empty?
32
- puts "Warning: React On Rails: expected to find .sprockets-manifest-*.json or manifest-*.json "\
33
- "at #{ReactOnRails::assets_path}, but found none. Canceling symlinking tasks."
34
- next
35
- end
36
- manifest_path = manifest_glob.first
37
- manifest_data = JSON.load(File.new(manifest_path))
38
-
39
- manifest_data["assets"].each do |logical_path, digested_path|
40
- regex = ReactOnRails.configuration.symlink_non_digested_assets_regex
41
- if logical_path =~ regex
42
- digested_gz_path = "#{digested_path}.gz"
43
- logical_gz_path = "#{logical_path}.gz"
44
- ReactOnRails::symlink_file(digested_path, logical_path)
45
- ReactOnRails::symlink_file(digested_gz_path, logical_gz_path)
46
- end
47
- end
48
- end
7
+ ReactOnRails::AssetsPrecompile.new.symlink_non_digested_assets
49
8
  end
50
9
 
51
10
  desc "Cleans all broken symlinks for the assets in the public asset dir"
52
11
  task delete_broken_symlinks: :"assets:environment" do
53
- Dir.glob(ReactOnRails::assets_path.join("*")).each do |filename|
54
- if File.lstat(filename).symlink?
55
- begin
56
- target = File.readlink(filename)
57
- rescue
58
- puts "React on Rails: Warning: your platform doesn't support File::readlink method."/
59
- "Skipping broken link check."
60
- return
61
- end
62
- path = Pathname.new(File.dirname(filename))
63
- target_path = path.join(target)
64
- unless File.exist?(target_path)
65
- puts "React on Rails: Deleting broken link: #{filename}"
66
- File.delete(filename)
67
- end
68
- end
69
- end
12
+ ReactOnRails::AssetsPrecompile.new.delete_broken_symlinks
70
13
  end
71
14
 
72
15
  # In this task, set prerequisites for the assets:precompile task
@@ -93,13 +36,7 @@ sh "cd client && `ReactOnRails.configuration.npm_build_production_command`"
93
36
 
94
37
  desc "Delete assets created with webpack, in the generated assetst directory (/app/assets/webpack)"
95
38
  task clobber: :environment do
96
- dir = Rails.root.join(ReactOnRails.configuration.generated_assets_dir)
97
- if dir.present? && File.directory?(dir)
98
- puts "Deleting files in directory #{dir}"
99
- rm_r Dir.glob(Rails.root.join("#{ReactOnRails.configuration.generated_assets_dir}/*"))
100
- else
101
- puts "Could not find dir #{dir}"
102
- end
39
+ ReactOnRails::AssetsPrecompile.new.clobber
103
40
  end
104
41
  end
105
42
  end
@@ -107,10 +44,10 @@ end
107
44
  # These tasks run as pre-requisites of assets:precompile.
108
45
  # Note, it's not possible to refer to ReactOnRails configuration values at this point.
109
46
  Rake::Task["assets:precompile"]
110
- .clear_prerequisites
111
- .enhance([:environment, "react_on_rails:assets:compile_environment"])
112
- .enhance do
113
- Rake::Task["react_on_rails:assets:symlink_non_digested_assets"].invoke
114
- Rake::Task["react_on_rails:assets:delete_broken_symlinks"].invoke
115
- end
47
+ .clear_prerequisites
48
+ .enhance([:environment, "react_on_rails:assets:compile_environment"])
49
+ .enhance do
50
+ Rake::Task["react_on_rails:assets:symlink_non_digested_assets"].invoke
51
+ Rake::Task["react_on_rails:assets:delete_broken_symlinks"].invoke
52
+ end
116
53