class Opal::Builder

Attributes

cache[RW]
compiler_options[RW]
missing_require_severity[RW]
path_reader[RW]
preload[RW]
prerequired[RW]
processed[R]
processors[RW]
scheduler[RW]
stubs[RW]

Public Class Methods

build(*args, &block) click to toggle source
# File lib/opal/builder.rb, line 87
def self.build(*args, &block)
  new.build(*args, &block)
end
extensions() click to toggle source

All the extensions supported by registered processors

# File lib/opal/builder.rb, line 18
def self.extensions
  @extensions ||= []
end
new(options = nil) click to toggle source
# File lib/opal/builder.rb, line 65
def initialize(options = nil)
  (options || {}).each_pair do |k, v|
    public_send("#{k}=", v)
  end

  @stubs                    ||= []
  @preload                  ||= []
  @processors               ||= ::Opal::Builder.processors
  @path_reader              ||= PathReader.new(Opal.paths, extensions.map { |e| [".#{e}", ".js.#{e}"] }.flatten)
  @prerequired              ||= []
  @compiler_options         ||= Opal::Config.compiler_options
  @missing_require_severity ||= Opal::Config.missing_require_severity
  @cache                    ||= Opal.cache
  @scheduler                ||= Opal.builder_scheduler

  if @scheduler.respond_to? :new
    @scheduler = @scheduler.new(self)
  end

  @processed = []
end
processors() click to toggle source

The registered processors

# File lib/opal/builder.rb, line 13
def self.processors
  @processors ||= []
end
register_processor(processor, processor_extensions) click to toggle source

@public Register a builder processor and the supported extensions. A processor will respond to:

## ‘#requires` An array of string containing the logic paths of required assets

## ‘#required_trees` An array of string containing the logic paths of required directories

## ‘#autoloads` An array of entities that are autoloaded and their compile-time load failure can be safely ignored

## ‘#to_s` The processed source

## ‘#source_map` An instance of `::Opal::SourceMap::File` representing the processd asset’s source map.

## ‘.new(source, filename, compiler_options)` The processor will be instantiated passing:

  • the unprocessed source

  • the asset’s filename

  • Opal’s compiler options

## ‘.match?(path)` The processor is able to recognize paths suitable for its type of processing.

# File lib/opal/builder.rb, line 53
def self.register_processor(processor, processor_extensions)
  return if processors.include?(processor)
  processors << processor
  processor_extensions.each { |ext| extensions << ext }
end

Public Instance Methods

already_processed() click to toggle source
# File lib/opal/builder.rb, line 170
def already_processed
  @already_processed ||= Set.new
end
append_paths(*paths) click to toggle source
# File lib/opal/builder.rb, line 137
def append_paths(*paths)
  path_reader.append_paths(*paths)
end
build(path, options = {}) click to toggle source
# File lib/opal/builder.rb, line 91
def build(path, options = {})
  build_str(source_for(path), path, options)
end
build_require(path, options = {}) click to toggle source
# File lib/opal/builder.rb, line 112
def build_require(path, options = {})
  process_require(path, [], options)
end
build_str(source, rel_path, options = {}) click to toggle source
# File lib/opal/builder.rb, line 100
def build_str(source, rel_path, options = {})
  return if source.nil?
  abs_path = expand_path(rel_path)
  rel_path = expand_ext(rel_path)
  asset = processor_for(source, rel_path, abs_path, false, options)
  requires = preload + asset.requires + tree_requires(asset, abs_path)
  # Don't automatically load modules required by the module
  process_requires(rel_path, requires, asset.autoloads, options.merge(load: false))
  processed << asset
  self
end
dependent_files() click to toggle source

Return a list of dependent files, for watching purposes

# File lib/opal/builder.rb, line 196
def dependent_files
  processed.map(&:abs_path).compact.select { |fn| File.exist?(fn) }
end
esm?() click to toggle source
# File lib/opal/builder.rb, line 181
def esm?
  @compiler_options[:esm]
end
expand_ext(path) click to toggle source
# File lib/opal/builder.rb, line 200
def expand_ext(path)
  abs_path = path_reader.expand(path)

  if abs_path
    File.join(
      File.dirname(path),
      File.basename(abs_path)
    )
  else
    path
  end
end
initialize_copy(other) click to toggle source
Calls superclass method
# File lib/opal/builder.rb, line 116
def initialize_copy(other)
  super
  @stubs = other.stubs.dup
  @preload = other.preload.dup
  @processors = other.processors.dup
  @path_reader = other.path_reader.dup
  @prerequired = other.prerequired.dup
  @compiler_options = other.compiler_options.dup
  @missing_require_severity = other.missing_require_severity.to_sym
  @processed = other.processed.dup
  @scheduler = other.scheduler.dup.tap { |i| i.builder = self }
end
output_extension() click to toggle source

Output extension, to be used by runners. At least Node.JS switches to ESM mode only if the extension is “mjs”

# File lib/opal/builder.rb, line 187
def output_extension
  if esm?
    'mjs'
  else
    'js'
  end
end
process_require(rel_path, autoloads, options) click to toggle source
# File lib/opal/builder.rb, line 163
def process_require(rel_path, autoloads, options)
  return if already_processed.include?(rel_path)
  already_processed << rel_path
  asset = process_require_threadsafely(rel_path, autoloads, options)
  processed << asset if asset
end
process_require_threadsafely(rel_path, autoloads, options) click to toggle source
# File lib/opal/builder.rb, line 141
def process_require_threadsafely(rel_path, autoloads, options)
  return if prerequired.include?(rel_path)

  autoload = autoloads.include? rel_path

  source = stub?(rel_path) ? '' : read(rel_path, autoload)

  # The handling is delegated to the runtime
  return if source.nil?

  abs_path = expand_path(rel_path)
  rel_path = expand_ext(rel_path)
  asset = processor_for(source, rel_path, abs_path, autoload, options.merge(requirable: true))
  process_requires(
    rel_path,
    asset.requires + tree_requires(asset, abs_path),
    asset.autoloads,
    options
  )
  asset
end
source_for(path) click to toggle source

Retrieve the source for a given path the same way build would do.

# File lib/opal/builder.rb, line 96
def source_for(path)
  read(path, false)
end
source_map() click to toggle source
# File lib/opal/builder.rb, line 133
def source_map
  ::Opal::SourceMap::Index.new(processed.map(&:source_map), join: "\n")
end
to_s() click to toggle source
# File lib/opal/builder.rb, line 129
def to_s
  processed.map(&:to_s).join("\n")
end

Private Instance Methods

expand_path(path) click to toggle source
# File lib/opal/builder.rb, line 277
def expand_path(path)
  return if stub?(path)
  (path_reader.expand(path) || File.expand_path(path)).to_s
end
extensions() click to toggle source
# File lib/opal/builder.rb, line 286
def extensions
  ::Opal::Builder.extensions
end
process_requires(rel_path, requires, autoloads, options) click to toggle source
# File lib/opal/builder.rb, line 215
def process_requires(rel_path, requires, autoloads, options)
  @scheduler.process_requires(rel_path, requires, autoloads, options)
end
processor_for(source, rel_path, abs_path, autoload, options) click to toggle source
# File lib/opal/builder.rb, line 238
def processor_for(source, rel_path, abs_path, autoload, options)
  processor = processors.find { |p| p.match? abs_path }

  if !processor && !autoload
    raise(ProcessorNotFound, "can't find processor for rel_path: " \
                             "#{rel_path.inspect}, "\
                             "abs_path: #{abs_path.inspect}, "\
                             "source: #{source.inspect}, "\
                             "processors: #{processors.inspect}"
         )
  end

  options = options.merge(cache: cache)

  processor.new(source, rel_path, abs_path, @compiler_options.merge(options))
end
read(path, autoload) click to toggle source
# File lib/opal/builder.rb, line 255
def read(path, autoload)
  path_reader.read(path) || begin
    print_list = ->(list) { "- #{list.join("\n- ")}\n" }
    message = "can't find file: #{path.inspect} in:\n" +
              print_list[path_reader.paths] +
              "\nWith the following extensions:\n" +
              print_list[path_reader.extensions] +
              "\nAnd the following processors:\n" +
              print_list[processors]

    unless autoload
      case missing_require_severity
      when :error   then raise MissingRequire, message
      when :warning then warn message
      when :ignore  then # noop
      end
    end

    nil
  end
end
stub?(path) click to toggle source
# File lib/opal/builder.rb, line 282
def stub?(path)
  stubs.include?(path)
end
tree_requires(asset, asset_path) click to toggle source
# File lib/opal/builder.rb, line 219
def tree_requires(asset, asset_path)
  dirname = asset_path.to_s.empty? ? Pathname.pwd : Pathname(asset_path).expand_path.dirname
  abs_base_paths = path_reader.paths.map { |p| File.expand_path(p) }

  asset.required_trees.flat_map do |tree|
    abs_tree_path = dirname.join(tree).expand_path.to_s
    abs_base_path = abs_base_paths.find { |p| abs_tree_path.start_with?(p) }

    if abs_base_path
      abs_base_path = Pathname(abs_base_path)
      entries_glob  = Pathname(abs_tree_path).join('**', "*{.js,}.{#{extensions.join ','}}")

      Pathname.glob(entries_glob).map { |file| file.relative_path_from(abs_base_path).to_s }
    else
      [] # the tree is not part of any known base path
    end
  end
end