Class: ReactOnRailsPro::PreSeedRendererCache

Inherits:
Object
  • Object
show all
Defined in:
lib/react_on_rails_pro/pre_seed_renderer_cache.rb

Overview

Stages the Node Renderer bundle cache in the renderer’s expected directory structure (‘<cache>/<bundleHash>/<bundleHash>.js`), including any configured assets_to_copy and, when RSC support is enabled, the RSC bundle and manifests.

Supports two modes:

  • ‘:copy` - copies bundle and assets. Designed for Docker image builds where the cache must be baked into an immutable artifact.

  • ‘:symlink` - creates relative symlinks. For same-filesystem workflows (local dev, CI, Heroku-style same-dyno deploys, bundle-caching restores).

Both modes produce the same on-disk cache layout, matching the renderer’s runtime contract. The 410->retry cold-start round-trip on first SSR request is eliminated when the pre-seeded bundle is present at renderer startup.

Constant Summary collapse

VALID_MODES =
%i[copy symlink].freeze

Class Method Summary collapse

Class Method Details

.call(mode:) ⇒ Object

‘mode:` is required (no default) because the two modes have fundamentally different semantics — `:copy` bakes immutable artifacts for Docker/image builds; `:symlink` links live files on a shared filesystem. Forcing callers to be explicit prevents the footgun where an implicit default would mismatch the deploy context (e.g., copy-mode raises about RENDERER_SERVER_BUNDLE_CACHE_PATH in a dev shell). The rake task and AssetsPrecompile auto-invocation both pass `mode:` explicitly with their own context-appropriate defaults.



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/react_on_rails_pro/pre_seed_renderer_cache.rb', line 33

def self.call(mode:)
  unless VALID_MODES.include?(mode)
    raise ArgumentError, "mode must be one of #{VALID_MODES.inspect}, got #{mode.inspect}"
  end

  cache_dir = resolve_cache_dir(mode)
  puts "[ReactOnRailsPro] Staging renderer cache (mode: #{mode}) in: #{cache_dir}"
  pool = ReactOnRailsPro::ServerRenderingPool::NodeRenderingPool

  assets, rsc_required_paths = RendererCacheHelpers.collect_assets_with_required_paths

  current_hashes = []
  # Block-level `rescue` (Ruby 2.5+): equivalent to wrapping the block body in
  # begin/rescue/end. RuboCop's Style/RedundantBegin enforces this form, so
  # callers reading the loop should treat the rescue clause below as the
  # iteration body's exception handler — not the surrounding method's.
  RendererCacheHelpers.bundle_sources(pool, action_description(mode)).each do |src_bundle_path, bundle_hash|
    bundle_dir = File.join(cache_dir, bundle_hash.to_s)
    stage_bundle(src_bundle_path, bundle_dir, bundle_hash, mode)
    # The Node Renderer serves manifests from whichever bundle dir it loaded,
    # so both server and RSC dirs need the manifests present.
    stage_assets(assets, bundle_dir, rsc_required_paths, mode)
    current_hashes << bundle_hash.to_s
  rescue StandardError => e
    # Fail-fast: re-raise on the first bundle failure so the deploy sees a non-zero exit and
    # aborts before downstream steps assume the cache is complete. Earlier bundles that
    # already staged successfully (e.g. server bundle when RSC fails) remain on disk for
    # diagnosis or for a re-run, but the renderer should not be expected to start from a
    # partially-staged cache — operators must rebuild the cache or roll back.
    warn "[ReactOnRailsPro] Renderer cache staging failed for bundle #{bundle_hash}; " \
         "cache may be partially staged: #{e.message}"
    raise
  end

  # Optionally seed previous deploys' bundle hashes for rolling-deploy safety.
  # No-op when neither config.rolling_deploy_adapter nor PREVIOUS_BUNDLE_HASHES is set.
  RollingDeployCacheStager.call(cache_dir: cache_dir, current_hashes: current_hashes, mode: mode)
end