react_on_rails 17.0.0.rc.0 → 17.0.0.rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -0
- data/Gemfile.lock +2 -2
- data/lib/generators/react_on_rails/base_generator.rb +28 -3
- data/lib/generators/react_on_rails/generator_helper.rb +82 -27
- data/lib/generators/react_on_rails/generator_messages/ci_section.rb +9 -1
- data/lib/generators/react_on_rails/install_generator.rb +48 -21
- data/lib/generators/react_on_rails/rsc_setup/client_references.rb +76 -12
- data/lib/generators/react_on_rails/rsc_setup.rb +19 -0
- data/lib/generators/react_on_rails/shakapacker_precompile_hook_helper.rb +160 -0
- data/lib/generators/react_on_rails/templates/base/base/.github/workflows/ci.yml.tt +7 -1
- data/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +1 -1
- data/lib/react_on_rails/config_path_resolver.rb +0 -2
- data/lib/react_on_rails/dev/server_manager.rb +260 -44
- data/lib/react_on_rails/dev/server_mode.rb +211 -0
- data/lib/react_on_rails/dev.rb +1 -0
- data/lib/react_on_rails/doctor.rb +169 -32
- data/lib/react_on_rails/engine.rb +9 -2
- data/lib/react_on_rails/length_prefixed_parser.rb +5 -4
- data/lib/react_on_rails/packs_generator.rb +24 -8
- data/lib/react_on_rails/pro_helper.rb +2 -0
- data/lib/react_on_rails/system_checker.rb +48 -16
- data/lib/react_on_rails/test_helper/dev_assets_detector.rb +19 -17
- data/lib/react_on_rails/test_helper/ensure_assets_compiled.rb +2 -2
- data/lib/react_on_rails/test_helper/webpack_assets_compiler.rb +4 -4
- data/lib/react_on_rails/test_helper/webpack_assets_status_checker.rb +2 -2
- data/lib/react_on_rails/test_helper.rb +6 -6
- data/lib/react_on_rails/version.rb +1 -1
- data/rakelib/shakapacker_version.rake +4 -1
- data/react_on_rails.gemspec +2 -2
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0feb399078c3e60a35b5e36bc257cc2711bf793fd04dab39c3ccfcfdd8448cd8
|
|
4
|
+
data.tar.gz: 3848ca13d934d2fda5ca63f8b7660aaac360ac3daec07d7a15433a647d058c44
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3d2a9e86976672ce1b5ae6b869a9d777bb652b35423cb7b361614db8ccdb710423ad8434115f460ffd7522336a9eb8eec74382bd793f4cd537dfe2d12b69df46
|
|
7
|
+
data.tar.gz: b75324ae9ad146861baebd20716ef293c28509734afbf206da63275db4d29c6a24115a9e2b834781d275a1a586fd9215cdd208c1e93a1101a47d275436cbfda1
|
data/.rubocop.yml
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -6,11 +6,13 @@ require "erb"
|
|
|
6
6
|
require_relative "generator_messages"
|
|
7
7
|
require_relative "generator_helper"
|
|
8
8
|
require_relative "js_dependency_manager"
|
|
9
|
+
require_relative "shakapacker_precompile_hook_helper"
|
|
9
10
|
module ReactOnRails
|
|
10
11
|
module Generators
|
|
11
12
|
class BaseGenerator < Rails::Generators::Base
|
|
12
13
|
include GeneratorHelper
|
|
13
14
|
include JsDependencyManager
|
|
15
|
+
include ShakapackerPrecompileHookHelper
|
|
14
16
|
|
|
15
17
|
Rails::Generators.hide_namespace(namespace)
|
|
16
18
|
source_root(File.expand_path("templates", __dir__))
|
|
@@ -22,11 +24,22 @@ module ReactOnRails
|
|
|
22
24
|
desc: "Install Redux package and Redux version of Hello World Example",
|
|
23
25
|
aliases: "-R"
|
|
24
26
|
|
|
25
|
-
# --rspack
|
|
27
|
+
# --rspack / --no-rspack (Rspack is the default on fresh installs; --no-rspack selects Webpack)
|
|
28
|
+
# IMPORTANT: do NOT add a `default:` here. The absence of a default is load-bearing — Thor
|
|
29
|
+
# only includes :rspack in the options hash when the flag is explicitly passed, which is how
|
|
30
|
+
# GeneratorHelper#using_rspack? tells an explicit choice from "no flag given" (the latter
|
|
31
|
+
# falls back to rspack_bundler_default). Adding `default: false` would make
|
|
32
|
+
# options.key?(:rspack) always true and silently break the fresh-install Rspack default.
|
|
33
|
+
# (Thor's omit-when-no-default behavior verified against Thor 1.5.0; see Gemfile.lock.)
|
|
26
34
|
class_option :rspack,
|
|
27
35
|
type: :boolean,
|
|
28
|
-
|
|
29
|
-
|
|
36
|
+
desc: "Use Rspack (default) as the bundler; pass --no-rspack to use Webpack"
|
|
37
|
+
|
|
38
|
+
# --webpack: friendly alias for --no-rspack (reconciled in GeneratorHelper#explicit_bundler_choice).
|
|
39
|
+
# No `default:` here either — same load-bearing reason as --rspack above.
|
|
40
|
+
class_option :webpack,
|
|
41
|
+
type: :boolean,
|
|
42
|
+
desc: "Use Webpack as the bundler (alias for --no-rspack; --no-webpack is equivalent to --rspack)"
|
|
30
43
|
|
|
31
44
|
# --pro
|
|
32
45
|
class_option :pro,
|
|
@@ -313,6 +326,18 @@ module ReactOnRails
|
|
|
313
326
|
|
|
314
327
|
private
|
|
315
328
|
|
|
329
|
+
# Fresh-install context: default to Rspack (when Shakapacker supports it) unless the
|
|
330
|
+
# app already declares a bundler. See GeneratorHelper#fresh_install_rspack_default.
|
|
331
|
+
# NOTE: InstallGenerator#rspack_bundler_default is an intentional twin of this override
|
|
332
|
+
# (both generators are independently CLI-invocable); keep the two in sync.
|
|
333
|
+
def rspack_bundler_default
|
|
334
|
+
fresh_install_rspack_default
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def generated_build_test_command
|
|
338
|
+
shakapacker_build_command(env: "RAILS_ENV=test NODE_ENV=test", environment: "test")
|
|
339
|
+
end
|
|
340
|
+
|
|
316
341
|
def generate_new_app_home_page?
|
|
317
342
|
options.new_app? && new_app_root_route_added?
|
|
318
343
|
end
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
|
+
require_relative "shakapacker_precompile_hook_helper"
|
|
4
5
|
|
|
6
|
+
# rubocop:disable Metrics/ModuleLength
|
|
5
7
|
module GeneratorHelper
|
|
8
|
+
include ReactOnRails::Generators::ShakapackerPrecompileHookHelper
|
|
9
|
+
|
|
6
10
|
def package_json
|
|
7
11
|
# Lazy load package_json gem only when actually needed for dependency management
|
|
8
12
|
|
|
@@ -128,11 +132,57 @@ module GeneratorHelper
|
|
|
128
132
|
def using_rspack?
|
|
129
133
|
return @using_rspack if defined?(@using_rspack)
|
|
130
134
|
|
|
131
|
-
#
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
|
|
135
|
-
@using_rspack =
|
|
135
|
+
# An explicit bundler flag always wins. When none was passed (or the generator doesn't
|
|
136
|
+
# declare the flags, e.g. RscGenerator/ProGenerator), fall back to the bundler default,
|
|
137
|
+
# which each generator defines for its own context.
|
|
138
|
+
explicit = explicit_bundler_choice
|
|
139
|
+
@using_rspack = explicit.nil? ? rspack_bundler_default : explicit
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Resolve the explicit bundler flags into a single choice.
|
|
143
|
+
#
|
|
144
|
+
# --rspack selects Rspack; --no-rspack and --webpack select Webpack (--webpack is a friendly
|
|
145
|
+
# alias for --no-rspack, and the auto-generated --no-webpack mirrors --rspack). Returns true
|
|
146
|
+
# for Rspack, false for Webpack, or nil when no bundler flag was passed (so the caller falls
|
|
147
|
+
# back to rspack_bundler_default).
|
|
148
|
+
#
|
|
149
|
+
# IMPORTANT: this relies on Thor NOT including a nil-defaulted option in the hash when the flag
|
|
150
|
+
# is absent — options.key?(:rspack)/(:webpack) is true only when the user passed that flag.
|
|
151
|
+
# Re-adding `default:` to either class_option would make the key always present and break both
|
|
152
|
+
# the "no flag given" fallback and the conflict detection here.
|
|
153
|
+
# (Thor's omit-when-no-default behavior verified against Thor 1.5.0; see Gemfile.lock.)
|
|
154
|
+
#
|
|
155
|
+
# Passing contradictory flags (e.g. --rspack --webpack) raises a Thor::Error.
|
|
156
|
+
def explicit_bundler_choice
|
|
157
|
+
choices = []
|
|
158
|
+
choices << options[:rspack] if options.key?(:rspack)
|
|
159
|
+
# --webpack means "use Webpack" (rspack = false); --no-webpack means "use Rspack".
|
|
160
|
+
# Name the inverted webpack flag so the rspack-boolean intent reads directly.
|
|
161
|
+
rspack_via_webpack_flag = !options[:webpack]
|
|
162
|
+
choices << rspack_via_webpack_flag if options.key?(:webpack)
|
|
163
|
+
return nil if choices.empty?
|
|
164
|
+
|
|
165
|
+
if choices.uniq.length > 1
|
|
166
|
+
raise Thor::Error,
|
|
167
|
+
"Conflicting bundler flags: pass either Rspack (--rspack) or Webpack " \
|
|
168
|
+
"(--webpack / --no-rspack), not both."
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
choices.first
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# True when the user passed any explicit bundler flag
|
|
175
|
+
# (--rspack/--no-rspack/--webpack/--no-webpack).
|
|
176
|
+
def bundler_flag_given?
|
|
177
|
+
options.key?(:rspack) || options.key?(:webpack)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Bundler to use when no explicit bundler flag was passed.
|
|
181
|
+
# Default (standalone generators like RscGenerator/ProGenerator): respect the existing
|
|
182
|
+
# project's shakapacker.yml and never impose a bundler. InstallGenerator/BaseGenerator
|
|
183
|
+
# override this to default fresh installs to Rspack.
|
|
184
|
+
def rspack_bundler_default
|
|
185
|
+
rspack_configured_in_project?
|
|
136
186
|
end
|
|
137
187
|
|
|
138
188
|
# Remap a config path from config/webpack/ to config/rspack/ when using rspack.
|
|
@@ -291,25 +341,6 @@ module GeneratorHelper
|
|
|
291
341
|
shakapacker_version_9_3_or_higher?
|
|
292
342
|
end
|
|
293
343
|
|
|
294
|
-
def parse_shakapacker_yml(path)
|
|
295
|
-
require "yaml"
|
|
296
|
-
# Use safe_load_file for security (defense-in-depth, even though this is user's own config)
|
|
297
|
-
# permitted_classes: [Symbol] allows symbol keys which shakapacker.yml may use
|
|
298
|
-
# aliases: true allows YAML anchors (&default, *default) commonly used in Rails configs
|
|
299
|
-
YAML.safe_load_file(path, permitted_classes: [Symbol], aliases: true)
|
|
300
|
-
rescue ArgumentError
|
|
301
|
-
# Older Psych versions don't support all parameters - try without aliases
|
|
302
|
-
begin
|
|
303
|
-
YAML.safe_load_file(path, permitted_classes: [Symbol])
|
|
304
|
-
rescue ArgumentError
|
|
305
|
-
# Very old Psych - fall back to safe_load with File.read
|
|
306
|
-
YAML.safe_load(File.read(path), permitted_classes: [Symbol]) # rubocop:disable Style/YAMLFileRead
|
|
307
|
-
end
|
|
308
|
-
rescue StandardError
|
|
309
|
-
# If we can't parse the file, return empty config
|
|
310
|
-
{}
|
|
311
|
-
end
|
|
312
|
-
|
|
313
344
|
# Check if Shakapacker 9.3.0 or higher is available
|
|
314
345
|
# This version made SWC the default JavaScript transpiler
|
|
315
346
|
def shakapacker_version_9_3_or_higher?
|
|
@@ -330,10 +361,34 @@ module GeneratorHelper
|
|
|
330
361
|
# `assets_bundler` inside the `default: &default` block, and our generator writes
|
|
331
362
|
# it there too via configure_rspack_in_shakapacker.
|
|
332
363
|
def rspack_configured_in_project?
|
|
364
|
+
shakapacker_assets_bundler_value == "rspack"
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
# Fresh-install bundler default used by InstallGenerator/BaseGenerator: prefer Rspack
|
|
368
|
+
# when Shakapacker supports it (Rspack landed in Shakapacker 9.0), but never override an
|
|
369
|
+
# existing app's explicit assets_bundler choice. On a brand-new install where Shakapacker
|
|
370
|
+
# isn't loaded yet, shakapacker_version_9_or_higher? optimistically returns true.
|
|
371
|
+
def fresh_install_rspack_default
|
|
372
|
+
return rspack_configured_in_project? if project_declares_assets_bundler?
|
|
373
|
+
|
|
374
|
+
shakapacker_version_9_or_higher?
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# True when config/shakapacker.yml exists and its default: section declares an
|
|
378
|
+
# assets_bundler (i.e., the project has already made an explicit bundler choice).
|
|
379
|
+
def project_declares_assets_bundler?
|
|
380
|
+
!shakapacker_assets_bundler_value.nil?
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# Single source for the config/shakapacker.yml default-section read shared by
|
|
384
|
+
# rspack_configured_in_project? and project_declares_assets_bundler?. Returns the
|
|
385
|
+
# assets_bundler value (e.g. "rspack"), or nil when the file is absent or the key is unset.
|
|
386
|
+
# Only the default: section is inspected (see rspack_configured_in_project? for the rationale).
|
|
387
|
+
def shakapacker_assets_bundler_value
|
|
333
388
|
shakapacker_yml_path = File.join(destination_root, "config/shakapacker.yml")
|
|
334
|
-
return
|
|
389
|
+
return nil unless File.exist?(shakapacker_yml_path)
|
|
335
390
|
|
|
336
|
-
|
|
337
|
-
config.dig("default", "assets_bundler") == "rspack"
|
|
391
|
+
parse_shakapacker_yml(shakapacker_yml_path).dig("default", "assets_bundler")
|
|
338
392
|
end
|
|
339
393
|
end
|
|
394
|
+
# rubocop:enable Metrics/ModuleLength
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "rainbow"
|
|
4
|
+
require_relative "../shakapacker_precompile_hook_helper"
|
|
4
5
|
|
|
5
6
|
module GeneratorMessages
|
|
6
7
|
module CiSection
|
|
8
|
+
include ReactOnRails::Generators::ShakapackerPrecompileHookHelper
|
|
9
|
+
|
|
7
10
|
private
|
|
8
11
|
|
|
9
12
|
def build_ci_section(app_root: Dir.pwd, ci_workflow_generated: false)
|
|
@@ -25,6 +28,11 @@ module GeneratorMessages
|
|
|
25
28
|
else
|
|
26
29
|
""
|
|
27
30
|
end
|
|
31
|
+
manual_build_command = shakapacker_build_command(
|
|
32
|
+
env: "RAILS_ENV=test NODE_ENV=test",
|
|
33
|
+
app_root: app_root,
|
|
34
|
+
environment: "test"
|
|
35
|
+
)
|
|
28
36
|
|
|
29
37
|
<<~CI
|
|
30
38
|
|
|
@@ -35,7 +43,7 @@ module GeneratorMessages
|
|
|
35
43
|
#{ci_status}
|
|
36
44
|
|
|
37
45
|
To build bundles manually before tests:
|
|
38
|
-
#{Rainbow(
|
|
46
|
+
#{Rainbow(manual_build_command).cyan}#{build_test_hint}
|
|
39
47
|
CI
|
|
40
48
|
end
|
|
41
49
|
end
|
|
@@ -9,6 +9,7 @@ require_relative "generator_messages"
|
|
|
9
9
|
require_relative "js_dependency_manager"
|
|
10
10
|
require_relative "pro_setup"
|
|
11
11
|
require_relative "rsc_setup"
|
|
12
|
+
require_relative "shakapacker_precompile_hook_helper"
|
|
12
13
|
# Load-path require: git_utils lives under react_on_rails/lib, not relative to this generator directory.
|
|
13
14
|
require "react_on_rails/git_utils"
|
|
14
15
|
|
|
@@ -23,6 +24,7 @@ module ReactOnRails
|
|
|
23
24
|
include JsDependencyManager
|
|
24
25
|
include ProSetup
|
|
25
26
|
include RscSetup
|
|
27
|
+
include ShakapackerPrecompileHookHelper
|
|
26
28
|
|
|
27
29
|
# fetch USAGE file for details generator description
|
|
28
30
|
source_root(File.expand_path(__dir__))
|
|
@@ -41,11 +43,22 @@ module ReactOnRails
|
|
|
41
43
|
desc: "Generate TypeScript files and install TypeScript dependencies. Default: false",
|
|
42
44
|
aliases: "-T"
|
|
43
45
|
|
|
44
|
-
# --rspack
|
|
46
|
+
# --rspack / --no-rspack (Rspack is the default on fresh installs; --no-rspack selects Webpack)
|
|
47
|
+
# IMPORTANT: do NOT add a `default:` here. The absence of a default is load-bearing — Thor
|
|
48
|
+
# only includes :rspack in the options hash when the flag is explicitly passed, which is how
|
|
49
|
+
# GeneratorHelper#using_rspack? tells an explicit choice from "no flag given" (the latter
|
|
50
|
+
# falls back to rspack_bundler_default). Adding `default: false` would make
|
|
51
|
+
# options.key?(:rspack) always true and silently break the fresh-install Rspack default.
|
|
52
|
+
# (Thor's omit-when-no-default behavior verified against Thor 1.5.0; see Gemfile.lock.)
|
|
45
53
|
class_option :rspack,
|
|
46
54
|
type: :boolean,
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
desc: "Use Rspack (default) as the bundler; pass --no-rspack to use Webpack"
|
|
56
|
+
|
|
57
|
+
# --webpack: friendly alias for --no-rspack (reconciled in GeneratorHelper#explicit_bundler_choice).
|
|
58
|
+
# No `default:` here either — same load-bearing reason as --rspack above.
|
|
59
|
+
class_option :webpack,
|
|
60
|
+
type: :boolean,
|
|
61
|
+
desc: "Use Webpack as the bundler (alias for --no-rspack; --no-webpack is equivalent to --rspack)"
|
|
49
62
|
|
|
50
63
|
# --ignore-warnings
|
|
51
64
|
class_option :ignore_warnings,
|
|
@@ -87,7 +100,6 @@ module ReactOnRails
|
|
|
87
100
|
|
|
88
101
|
# Removed: --skip-shakapacker-install (Shakapacker is now a required dependency)
|
|
89
102
|
|
|
90
|
-
SHAKAPACKER_YML_PATH = "config/shakapacker.yml"
|
|
91
103
|
HELLO_WORLD_ROUTE = "hello_world"
|
|
92
104
|
HELLO_SERVER_ROUTE = "hello_server"
|
|
93
105
|
# Matches the stock `bin/dev` written by Rails 8.x. Rails 7.1 commonly
|
|
@@ -196,6 +208,14 @@ module ReactOnRails
|
|
|
196
208
|
|
|
197
209
|
private
|
|
198
210
|
|
|
211
|
+
# Fresh-install context: default to Rspack (when Shakapacker supports it) unless the
|
|
212
|
+
# app already declares a bundler. See GeneratorHelper#fresh_install_rspack_default.
|
|
213
|
+
# NOTE: BaseGenerator#rspack_bundler_default is an intentional twin of this override
|
|
214
|
+
# (both generators are independently CLI-invocable); keep the two in sync.
|
|
215
|
+
def rspack_bundler_default
|
|
216
|
+
fresh_install_rspack_default
|
|
217
|
+
end
|
|
218
|
+
|
|
199
219
|
def invoke_generators
|
|
200
220
|
ensure_shakapacker_installed
|
|
201
221
|
if options.typescript?
|
|
@@ -206,7 +226,7 @@ module ReactOnRails
|
|
|
206
226
|
# `invoke` instantiates child generators with a fresh options hash, so
|
|
207
227
|
# --pretend/--force/--skip must be forwarded explicitly at each boundary.
|
|
208
228
|
invoke "react_on_rails:base", [],
|
|
209
|
-
{ typescript: options.typescript?, redux: options.redux?, rspack:
|
|
229
|
+
{ typescript: options.typescript?, redux: options.redux?, rspack: using_rspack?,
|
|
210
230
|
pro: use_pro?, rsc: use_rsc?, new_app: options.new_app?,
|
|
211
231
|
shakapacker_just_installed: shakapacker_just_installed?,
|
|
212
232
|
force: options[:force], skip: options[:skip], pretend: options[:pretend] }
|
|
@@ -296,18 +316,19 @@ module ReactOnRails
|
|
|
296
316
|
{ package_manager: package_manager, has_lockfile: has_lockfile,
|
|
297
317
|
pnpm_version_declared: pnpm_version_declared,
|
|
298
318
|
pnpm_fallback_version: CI_PNPM_FALLBACK_VERSION,
|
|
299
|
-
has_active_record: has_active_record, has_rspec: has_rspec
|
|
319
|
+
has_active_record: has_active_record, has_rspec: has_rspec,
|
|
320
|
+
precompile_hook_command: shakapacker_precompile_hook_command(environment: "test") })
|
|
300
321
|
@ci_workflow_generated = true
|
|
301
322
|
end
|
|
302
323
|
|
|
303
|
-
#
|
|
304
|
-
#
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
324
|
+
# RAILS_ENV=production runs the hook with production Rails config, while
|
|
325
|
+
# NODE_ENV=production makes Shakapacker emit a minified production bundle.
|
|
326
|
+
def default_package_json_scripts
|
|
327
|
+
{
|
|
328
|
+
"build" => shakapacker_build_command(env: "RAILS_ENV=production NODE_ENV=production"),
|
|
329
|
+
"build:test" => shakapacker_build_command(env: "RAILS_ENV=test NODE_ENV=test", environment: "test")
|
|
330
|
+
}
|
|
331
|
+
end
|
|
311
332
|
|
|
312
333
|
def add_package_json_scripts
|
|
313
334
|
return if options[:pretend]
|
|
@@ -317,7 +338,7 @@ module ReactOnRails
|
|
|
317
338
|
|
|
318
339
|
original_text = File.read(package_json_path)
|
|
319
340
|
existing_scripts = JSON.parse(original_text)["scripts"] || {}
|
|
320
|
-
scripts_to_add =
|
|
341
|
+
scripts_to_add = default_package_json_scripts.reject { |key, _| existing_scripts.key?(key) }
|
|
321
342
|
|
|
322
343
|
if scripts_to_add.empty?
|
|
323
344
|
say_status :skip, "build scripts already present in package.json", :yellow
|
|
@@ -690,7 +711,10 @@ module ReactOnRails
|
|
|
690
711
|
flags = []
|
|
691
712
|
flags << "--redux" if options.redux?
|
|
692
713
|
flags << "--typescript" if options.typescript?
|
|
693
|
-
|
|
714
|
+
# Echo the resolved bundler choice (normalized to --rspack/--no-rspack, so a --webpack
|
|
715
|
+
# alias re-runs as --no-rspack) only when the user passed one explicitly. An unset choice
|
|
716
|
+
# re-resolves to the fresh-install default on re-run, so we don't pin it here.
|
|
717
|
+
flags << (using_rspack? ? "--rspack" : "--no-rspack") if bundler_flag_given?
|
|
694
718
|
|
|
695
719
|
if options.rsc?
|
|
696
720
|
flags << "--rsc"
|
|
@@ -838,11 +862,14 @@ module ReactOnRails
|
|
|
838
862
|
|
|
839
863
|
seed_package_manager_in_package_json_from_lockfile!
|
|
840
864
|
|
|
841
|
-
# Then run the shakapacker installer
|
|
842
|
-
#
|
|
843
|
-
#
|
|
844
|
-
#
|
|
845
|
-
|
|
865
|
+
# Then run the shakapacker installer.
|
|
866
|
+
# Resolve the bundler via using_rspack?. shakapacker.yml doesn't exist yet at this point,
|
|
867
|
+
# so the fresh-install default applies: an unset --rspack flag resolves to Rspack when
|
|
868
|
+
# Shakapacker supports it (shakapacker_version_9_or_higher? is optimistically true on a
|
|
869
|
+
# brand-new install where Shakapacker isn't loaded yet). An explicit --no-rspack still
|
|
870
|
+
# selects Webpack. using_rspack? memoizes, so the rest of the run (e.g.
|
|
871
|
+
# configure_rspack_in_shakapacker) stays consistent with this decision.
|
|
872
|
+
shakapacker_install_env = using_rspack? ? { "SHAKAPACKER_ASSETS_BUNDLER" => "rspack" } : {}
|
|
846
873
|
success = Bundler.with_unbundled_env do
|
|
847
874
|
system(shakapacker_install_env, "bundle exec rails shakapacker:install")
|
|
848
875
|
end
|
|
@@ -6,6 +6,8 @@ module ReactOnRails
|
|
|
6
6
|
module ClientReferences # rubocop:disable Metrics/ModuleLength
|
|
7
7
|
JS_STRING_DELIMITERS = ["'", '"', "`"].freeze
|
|
8
8
|
JS_COMMENT_STATES = %i[line_comment block_comment].freeze
|
|
9
|
+
# Characters that complete `//` or `/*` after a leading `/`.
|
|
10
|
+
JS_COMMENT_SECOND_CHARS = ["/", "*"].freeze
|
|
9
11
|
# Known limitation: this list only covers single-character regex preceders. Multi-character
|
|
10
12
|
# JavaScript keywords that legally precede a regex literal (`return`, `typeof`, `void`,
|
|
11
13
|
# `delete`, `throw`, `case`, `in`, `instanceof`) are not represented. A regex like `/\{/`
|
|
@@ -243,9 +245,9 @@ module ReactOnRails
|
|
|
243
245
|
# No parseable `isServer: <bool>` section means this file's plugin call sits outside
|
|
244
246
|
# what the generator's scanner can match (e.g. options are computed at runtime, or the
|
|
245
247
|
# plugin is invoked without an options object). Verification callers intentionally
|
|
246
|
-
#
|
|
247
|
-
#
|
|
248
|
-
#
|
|
248
|
+
# keep these out of the missing-pattern list because there is no literal section to
|
|
249
|
+
# classify as broken. They warn separately for dynamic invocations so users can verify
|
|
250
|
+
# those configs manually.
|
|
249
251
|
return true if sections.empty?
|
|
250
252
|
|
|
251
253
|
sections.all? do |section|
|
|
@@ -258,6 +260,30 @@ module ReactOnRails
|
|
|
258
260
|
end
|
|
259
261
|
end
|
|
260
262
|
|
|
263
|
+
# Counts active `new RSCWebpackPlugin(...)` calls whose first argument is present but
|
|
264
|
+
# not an object literal. This includes computed options plus static string/template
|
|
265
|
+
# literals; all are outside the verifier's parseable `{...}` contract.
|
|
266
|
+
def non_object_literal_rsc_plugin_invocation_count(content)
|
|
267
|
+
count = 0
|
|
268
|
+
search_from = 0
|
|
269
|
+
|
|
270
|
+
while (match = content.match(RSC_PLUGIN_INVOCATION_REGEX, search_from))
|
|
271
|
+
call_start = match.begin(0)
|
|
272
|
+
after_open_paren = match.end(0)
|
|
273
|
+
unless js_code_position?(content, call_start)
|
|
274
|
+
search_from = after_open_paren
|
|
275
|
+
next
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
options_start = first_js_token_index(content, after_open_paren)
|
|
279
|
+
options_start_char = options_start ? content[options_start] : nil
|
|
280
|
+
count += 1 if options_start_char && options_start_char != "{" && options_start_char != ")"
|
|
281
|
+
search_from = after_open_paren
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
count
|
|
285
|
+
end
|
|
286
|
+
|
|
261
287
|
def rsc_plugin_defines_client_references?(content, is_server:)
|
|
262
288
|
rsc_plugin_option_sections(content, is_server: is_server).any? do |section|
|
|
263
289
|
rsc_plugin_body_has_top_level_key?(section.fetch(:body), "clientReferences")
|
|
@@ -411,28 +437,66 @@ module ReactOnRails
|
|
|
411
437
|
false
|
|
412
438
|
end
|
|
413
439
|
|
|
440
|
+
# Skips whitespace and JS line/block comments, preserving strings as the next token.
|
|
441
|
+
# Non-object-literal option verification uses this so `new RSCWebpackPlugin("opts")`
|
|
442
|
+
# warns instead of having the string skipped as incidental syntax.
|
|
443
|
+
def first_js_token_index(content, start_index)
|
|
444
|
+
index = start_index
|
|
445
|
+
state = nil
|
|
446
|
+
escaped = false
|
|
447
|
+
|
|
448
|
+
while index < content.length
|
|
449
|
+
char = content[index]
|
|
450
|
+
next_char = content[index + 1]
|
|
451
|
+
|
|
452
|
+
if state
|
|
453
|
+
state, escaped, index = advance_js_scan_state(state, escaped, char, next_char, index)
|
|
454
|
+
index += 1
|
|
455
|
+
next
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
return index unless char.match?(/\s/) || (char == "/" && JS_COMMENT_SECOND_CHARS.include?(next_char))
|
|
459
|
+
|
|
460
|
+
state, escaped, index = advance_js_scan_state(state, escaped, char, next_char, index)
|
|
461
|
+
|
|
462
|
+
index += 1
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
nil
|
|
466
|
+
end
|
|
467
|
+
|
|
414
468
|
# Skips whitespace, JS line/block comments, and leading string literals so callers see the
|
|
415
469
|
# next structural character. Without comment skipping, configurations like
|
|
416
470
|
# `new RSCWebpackPlugin( /* opts */ {` would land on `/` and be rejected as "no plugin
|
|
417
471
|
# options" even though the options object is present.
|
|
418
472
|
def first_significant_js_index(content, start_index)
|
|
419
|
-
index = start_index
|
|
473
|
+
index = first_js_token_index(content, start_index)
|
|
474
|
+
|
|
475
|
+
while index && JS_STRING_DELIMITERS.include?(content[index])
|
|
476
|
+
string_end = js_string_end_index(content, index)
|
|
477
|
+
return nil unless string_end
|
|
478
|
+
|
|
479
|
+
index = first_js_token_index(content, string_end + 1)
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
index
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# Scans from a string-opening delimiter at `string_start_index` and returns the index
|
|
486
|
+
# of the matching closing delimiter, not one past it. Returns nil for unterminated
|
|
487
|
+
# strings. Callers must ensure `content[string_start_index]` is in JS_STRING_DELIMITERS.
|
|
488
|
+
def js_string_end_index(content, string_start_index)
|
|
420
489
|
state = nil
|
|
421
490
|
escaped = false
|
|
491
|
+
index = string_start_index
|
|
422
492
|
|
|
423
493
|
while index < content.length
|
|
424
494
|
char = content[index]
|
|
425
495
|
next_char = content[index + 1]
|
|
426
|
-
|
|
496
|
+
previous_state = state
|
|
427
497
|
state, escaped, index = advance_js_scan_state(state, escaped, char, next_char, index)
|
|
428
|
-
# Exiting a block comment leaves `char` as `*` and `index` pointing at the closing
|
|
429
|
-
# `/`; advance past it so the next iteration evaluates the first character after `*/`.
|
|
430
|
-
if state || prev_state == :block_comment
|
|
431
|
-
index += 1
|
|
432
|
-
next
|
|
433
|
-
end
|
|
434
498
|
|
|
435
|
-
return index
|
|
499
|
+
return index if previous_state && state.nil?
|
|
436
500
|
|
|
437
501
|
index += 1
|
|
438
502
|
end
|
|
@@ -538,6 +538,7 @@ module ReactOnRails
|
|
|
538
538
|
content = File.read(path)
|
|
539
539
|
missing = []
|
|
540
540
|
if content.include?("RSCWebpackPlugin")
|
|
541
|
+
warn_non_object_literal_rsc_plugin_options_for_config(content)
|
|
541
542
|
unless rsc_plugin_client_references_configured?(content, is_server: true)
|
|
542
543
|
missing << "generated scoped clientReferences in serverWebpackConfig.js"
|
|
543
544
|
end
|
|
@@ -555,6 +556,7 @@ module ReactOnRails
|
|
|
555
556
|
content = File.read(path)
|
|
556
557
|
missing = []
|
|
557
558
|
if content.include?("RSCWebpackPlugin")
|
|
559
|
+
warn_non_object_literal_rsc_plugin_options_for_config(content)
|
|
558
560
|
unless rsc_plugin_client_references_configured?(content, is_server: false)
|
|
559
561
|
missing << "generated scoped clientReferences in clientWebpackConfig.js"
|
|
560
562
|
end
|
|
@@ -571,6 +573,23 @@ module ReactOnRails
|
|
|
571
573
|
content = File.read(File.join(destination_root, scob_path))
|
|
572
574
|
content.include?("rscWebpackConfig") ? [] : ["rscWebpackConfig in ServerClientOrBoth.js"]
|
|
573
575
|
end
|
|
576
|
+
|
|
577
|
+
def warn_non_object_literal_rsc_plugin_options_for_config(content)
|
|
578
|
+
return unless non_object_literal_rsc_plugin_invocation_count(content).positive?
|
|
579
|
+
|
|
580
|
+
warn_non_object_literal_rsc_plugin_options_once
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
def warn_non_object_literal_rsc_plugin_options_once
|
|
584
|
+
return if @non_object_literal_rsc_plugin_options_warned
|
|
585
|
+
|
|
586
|
+
@non_object_literal_rsc_plugin_options_warned = true
|
|
587
|
+
GeneratorMessages.add_warning(
|
|
588
|
+
"RSCWebpackPlugin calls use non-object-literal options in one or more webpack configs, " \
|
|
589
|
+
"so the generator cannot verify whether scoped clientReferences are configured. " \
|
|
590
|
+
"Please verify your webpack configs manually."
|
|
591
|
+
)
|
|
592
|
+
end
|
|
574
593
|
end
|
|
575
594
|
end
|
|
576
595
|
end
|