class Opal::Builder
Attributes
Public Class Methods
# File lib/opal/builder.rb, line 87 def self.build(*args, &block) new.build(*args, &block) end
All the extensions supported by registered processors
# File lib/opal/builder.rb, line 18 def self.extensions @extensions ||= [] end
# 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
The registered processors
# File lib/opal/builder.rb, line 13 def self.processors @processors ||= [] end
@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
# File lib/opal/builder.rb, line 170 def already_processed @already_processed ||= Set.new end
# File lib/opal/builder.rb, line 137 def append_paths(*paths) path_reader.append_paths(*paths) end
# File lib/opal/builder.rb, line 91 def build(path, options = {}) build_str(source_for(path), path, options) end
# File lib/opal/builder.rb, line 112 def build_require(path, options = {}) process_require(path, [], options) end
# 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
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
# File lib/opal/builder.rb, line 181 def esm? @compiler_options[:esm] end
# 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
# 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, 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
# 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
# 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
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
# File lib/opal/builder.rb, line 133 def source_map ::Opal::SourceMap::Index.new(processed.map(&:source_map), join: "\n") end
# File lib/opal/builder.rb, line 129 def to_s processed.map(&:to_s).join("\n") end
Private Instance Methods
# 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
# File lib/opal/builder.rb, line 286 def extensions ::Opal::Builder.extensions end
# File lib/opal/builder.rb, line 215 def process_requires(rel_path, requires, autoloads, options) @scheduler.process_requires(rel_path, requires, autoloads, options) end
# 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
# 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
# File lib/opal/builder.rb, line 282 def stub?(path) stubs.include?(path) end
# 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