Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -121,36 +121,41 @@ def js_line_ending(content)
end

def update_existing_rsc_webpack_config(config_path, content, is_server:)
return unless rsc_plugin_sections_safe_to_rewrite?(config_path, content, is_server: is_server)
return unless rsc_plugin_sections_safe_to_rewrite?(config_path, content)
return if rsc_plugin_uses_scoped_client_references?(content, is_server: is_server)
return unless rsc_client_references_rewrite_needed?(config_path, content, is_server: is_server)

# May inject the scoped helper before the rewrite step re-reads the config from disk.
return unless prepare_rsc_client_references_setup(config_path, content, is_server: is_server)
Comment thread
justin808 marked this conversation as resolved.
return unless rsc_plugin_needs_client_references_rewrite?(content, is_server: is_server)

return if rewrite_rsc_plugin_client_references(config_path, is_server: is_server)

rollback_incomplete_rsc_client_references_setup(config_path, content)
warn_missing_rsc_plugin_target(config_path, is_server: is_server)
end

def rsc_client_references_rewrite_needed?(config_path, content, is_server:)
# This predicate prepares the rewrite too: when it returns true, the scoped helper
# may already have been injected on disk so `rewrite_rsc_plugin_client_references`
# can re-read fresh content with valid offsets.
def prepare_rsc_client_references_setup(config_path, content, is_server:)
if rsc_plugin_references_any_scoped_client_references?(content, is_server: is_server)
return false unless ensure_rsc_client_references_setup(config_path, content, is_server: is_server)

return rsc_plugin_without_client_references?(content, is_server: is_server)
return ensure_rsc_client_references_setup(config_path, content, is_server: is_server)
end

rewritable_rsc_plugin?(config_path, content, is_server: is_server) &&

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The behavioral equivalence with the old rsc_client_references_rewrite_needed? relies on a non-obvious invariant here: rewritable_rsc_plugin? returns true only when rsc_plugin_without_client_references? is also true (see its implementation at line ~165). So when prepare_rsc_client_references_setup returns true via this branch, rsc_plugin_needs_client_references_rewrite? will always agree — the extra predicate call is never a gate in this path.

The refactoring is correct, but since this invariant is load-bearing for correctness a short note here would help future readers:

Suggested change
rewritable_rsc_plugin?(config_path, content, is_server: is_server) &&
# `rewritable_rsc_plugin?` returns true only when `rsc_plugin_without_client_references?`
# is true, so the subsequent `rsc_plugin_needs_client_references_rewrite?` call in
# `update_existing_rsc_webpack_config` will always proceed from this branch.
rewritable_rsc_plugin?(config_path, content, is_server: is_server) &&
ensure_rsc_client_references_setup(config_path, content, is_server: is_server)

ensure_rsc_client_references_setup(config_path, content, is_server: is_server)
end

def rsc_plugin_needs_client_references_rewrite?(content, is_server:)
rsc_plugin_without_client_references?(content, is_server: is_server)
end

# Detects RSCWebpackPlugin option blocks that the lightweight JS scanner could not parse
# cleanly (most often a regex literal with an unmatched `{` / `}` that walks the depth
# counter past the real closing brace). When found, we warn and refuse to rewrite anything
# in the file so a sibling rewrite cannot accidentally splice into a wrong location.
def rsc_plugin_sections_safe_to_rewrite?(config_path, content, is_server:)
unparseable = rsc_plugin_option_sections_partition(content, is_server: is_server).fetch(:unparseable)
def rsc_plugin_sections_safe_to_rewrite?(config_path, content)
# The unparseable count is file-wide: the partition increments it for every invocation
# that cannot be parsed, regardless of the `is_server` argument. The argument only
# filters the target-specific safe bucket, which is ignored here.
unparseable = rsc_plugin_option_sections_partition(content, is_server: true).fetch(:unparseable)
Comment thread
justin808 marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The is_server: true value is arbitrary — confirmed by reading rsc_plugin_option_sections_partition: the unparseable counter increments before the rsc_plugin_is_server_match? filter, so the count is identical regardless of which boolean you pass. The comment explains this well.

One minor nit: is_server: true looks like a meaningful choice to a reader who hasn't yet read the comment. Consider an explicit signal that the value doesn't matter, e.g.:

Suggested change
unparseable = rsc_plugin_option_sections_partition(content, is_server: true).fetch(:unparseable)
unparseable = rsc_plugin_option_sections_partition(content, is_server: false).fetch(:unparseable)

false is equally correct and slightly less likely to be read as "we want the server-side sections" by someone skimming. Either value works — just flagging for discussion.

return true if unparseable.zero?

warn_unparseable_rsc_plugin_sections(config_path, unparseable)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2069,7 +2069,7 @@
)
content = File.read(File.join(destination_root, config_path))

expect(generator.send(:rsc_plugin_sections_safe_to_rewrite?, config_path, content, is_server: true))
expect(generator.send(:rsc_plugin_sections_safe_to_rewrite?, config_path, content))
.to be(false)

messages = GeneratorMessages.messages.join("\n")
Expand Down Expand Up @@ -2113,6 +2113,57 @@
expect(false_partition.fetch(:safe).length).to eq(1)
end

it "keeps the client references rewrite predicate free of file writes" do
config_path = "config/webpack/clientWebpackConfig.js"
simulate_existing_file(
config_path,
<<~JS
const commonWebpackConfig = require('./commonWebpackConfig');
const { RSCWebpackPlugin } = require('react-on-rails-rsc/WebpackPlugin');

const configureClient = () => {
const clientConfig = commonWebpackConfig();
clientConfig.plugins.push(
new RSCWebpackPlugin({
isServer: false,
clientReferences: rscClientReferences,
}),
);

return clientConfig;
};

module.exports = configureClient;
JS
)

full_path = File.join(destination_root, config_path)
content = File.read(full_path)

expect(generator.send(:rsc_plugin_needs_client_references_rewrite?, content, is_server: false))
.to be(false)
expect(File.read(full_path)).to eq(content)
end
Comment thread
justin808 marked this conversation as resolved.

it "returns true from the client references rewrite predicate without file writes" do
config_path = "config/webpack/clientWebpackConfig.js"
simulate_existing_file(
config_path,
<<~JS
const { RSCWebpackPlugin } = require('react-on-rails-rsc/WebpackPlugin');

clientConfig.plugins.push(new RSCWebpackPlugin({ isServer: false }));
JS
)

full_path = File.join(destination_root, config_path)
content = File.read(full_path)

expect(generator.send(:rsc_plugin_needs_client_references_rewrite?, content, is_server: false))
.to be(true)
expect(File.read(full_path)).to eq(content)
end

it "does not inject duplicate imports for legacy let or var CommonJS destructuring" do
config_path = "config/webpack/clientWebpackConfig.js"
simulate_existing_file(
Expand Down
Loading