Module: FactoryBot::Instrumentation::AssetBundler

Defined in:
lib/factory_bot/instrumentation/asset_bundler.rb

Overview

A tiny, dependency-free replacement for Sprockets’ require_tree directive. It concatenates the JavaScript or CSS sources shipped with this engine into a single application.{js,css} asset so that the engine no longer needs an asset pipeline at runtime.

The bundles are regenerated in two situations:

  • During gem release, via the bundle_assets Rake task which is hooked into rake build.

  • On gem install / bundle install (including Git source installs in third-party applications), via the RubyGems extension shim at ext/factory_bot_instrumentation/extconf.rb.

Constant Summary collapse

ROOT_DIR =

Absolute path to the gem root (where the app and lib folders live), derived from this file’s location.

File.expand_path('../../..', __dir__)
KINDS =

Per-kind configuration. Each entry describes where the sources live, which extension to bundle, where to write the manifest and how to format the comments wrapping each chunk.

{
  js: {
    source_dir: File.join(
      ROOT_DIR, 'app', 'assets', 'javascripts',
      'factory_bot_instrumentation'
    ),
    output_name: 'application.js',
    extension: 'js',
    line_comment: '// %s',
    banner_format: :line
  },
  css: {
    source_dir: File.join(
      ROOT_DIR, 'app', 'assets', 'stylesheets',
      'factory_bot_instrumentation'
    ),
    output_name: 'application.css',
    extension: 'css',
    line_comment: '/* %s */',
    banner_format: :block
  }
}.freeze
<<~TEXT
  !!! AUTO-GENERATED FILE - DO NOT EDIT !!!

  This file bundles every %<kind>s source under
  +%<source_rel>s/+ into a single asset. It is regenerated:
    * on `gem install` / `bundle install` (via a RubyGems extension
      hook), and
    * during gem release (via the `bundle_assets` Rake task).

  To rebuild it manually, run:

    $ bundle exec rake bundle_assets
TEXT

Class Method Summary collapse

Class Method Details

Render the auto-generation banner in the comment style of kind. JavaScript bundles get line comments (+// …+), CSS bundles get a single block comment (+/* … */+).

Parameters:

  • kind (Symbol)

    one of :js or :css

Returns:

  • (String)

    the banner ready to be prepended to the bundle, including the trailing newline



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/factory_bot/instrumentation/asset_bundler.rb', line 173

def banner(kind)
  conf = config(kind)
  text = format(
    BANNER_TEMPLATE,
    kind: conf[:extension].upcase,
    source_rel: conf[:source_dir].sub("#{ROOT_DIR}/", '')
  )

  case conf[:banner_format]
  when :line
    lines = text.each_line
                .map { |l| format(conf[:line_comment], l.chomp).rstrip }
                .join("\n")
    "#{lines}\n"
  when :block
    body = text.each_line.map { |l| " * #{l.chomp}".rstrip }.join("\n")
    "/*\n#{body}\n */\n"
  end
end

.bundle!(kind) ⇒ Array<String>

Build the bundle for the given kind and write it to the corresponding manifest. The manifest itself is excluded from the input set so that re-runs are idempotent.

Parameters:

  • kind (Symbol)

    one of :js or :css

Returns:

  • (Array<String>)

    absolute paths of the source files that were concatenated, in the order they appear in the output

Raises:

  • (ArgumentError)

    if kind is not a known asset kind



82
83
84
85
86
87
88
89
90
# File 'lib/factory_bot/instrumentation/asset_bundler.rb', line 82

def bundle!(kind)
  conf = config(kind)
  files = sources(kind)
  File.write(
    File.join(conf[:source_dir], conf[:output_name]),
    banner(kind) + files.map { |f| chunk(kind, f) }.join
  )
  files
end

.bundle_all!Hash{Symbol => Array<String>}

Build every known asset kind in turn.

rubocop:disable Rails/IndexWith – because we’re

dependency-free in here

Returns:

  • (Hash{Symbol => Array<String>})

    a mapping of kind to the absolute paths of the source files that were concatenated for that kind



100
101
102
# File 'lib/factory_bot/instrumentation/asset_bundler.rb', line 100

def bundle_all!
  kinds.to_h { |kind| [kind, bundle!(kind)] }
end

.chunk(kind, path) ⇒ String

Wrap a single source file with a comment that names its relative path. The returned snippet starts with a newline so that chunks concatenate cleanly under the banner.

Parameters:

  • kind (Symbol)

    one of :js or :css

  • path (String)

    the absolute path of the source file to wrap

Returns:

  • (String)

    the comment-wrapped source contents



157
158
159
160
161
162
163
164
# File 'lib/factory_bot/instrumentation/asset_bundler.rb', line 157

def chunk(kind, path)
  conf = config(kind)
  body = File.read(path)
  body += "\n" unless body.end_with?("\n")
  marker = format(conf[:line_comment],
                  ">>> #{relative_source(kind, path)}")
  "\n#{marker}\n#{body}"
end

.config(kind) ⇒ Hash

Look up the configuration for kind, raising on unknown values.

Parameters:

  • kind (Symbol)

    the kind identifier to look up

Returns:

  • (Hash)

    the configuration entry from KINDS, with keys :source_dir, :output_name, :extension, :line_comment and :banner_format

Raises:

  • (ArgumentError)

    if kind is not a known asset kind



129
130
131
132
133
134
135
# File 'lib/factory_bot/instrumentation/asset_bundler.rb', line 129

def config(kind)
  KINDS.fetch(kind) do
    raise ArgumentError,
          "Unknown asset kind #{kind.inspect}; expected one of " \
          "#{KINDS.keys.inspect}"
  end
end

.kindsArray<Symbol>

All asset kinds known to the bundler.

Returns:

  • (Array<Symbol>)

    the registered kind identifiers



70
71
72
# File 'lib/factory_bot/instrumentation/asset_bundler.rb', line 70

def kinds
  KINDS.keys
end

.relative_source(kind, file) ⇒ String

Path of file relative to its kind’s source directory, used in the per-chunk comment marker so the bundled output remains debuggable.

Parameters:

  • kind (Symbol)

    one of :js or :css

  • file (String)

    the absolute path of a source file

Returns:

  • (String)

    file stripped of the kind’s source directory prefix



145
146
147
# File 'lib/factory_bot/instrumentation/asset_bundler.rb', line 145

def relative_source(kind, file)
  file.sub("#{config(kind)[:source_dir]}/", '')
end

.sources(kind) ⇒ Array<String>

All sources to include for kind, sorted so the result is stable across platforms and filesystems. The manifest itself is excluded to avoid recursive inclusion.

Parameters:

  • kind (Symbol)

    one of :js or :css

Returns:

  • (Array<String>)

    absolute paths of the matching source files, sorted ascending

Raises:

  • (ArgumentError)

    if kind is not a known asset kind



113
114
115
116
117
118
119
120
# File 'lib/factory_bot/instrumentation/asset_bundler.rb', line 113

def sources(kind)
  conf = config(kind)
  output = File.expand_path(File.join(conf[:source_dir],
                                      conf[:output_name]))
  Dir.glob(File.join(conf[:source_dir], '**', "*.#{conf[:extension]}"))
     .reject { |path| File.expand_path(path) == output }
     .sort
end