class Importmap::Map

Constants

MappedDir
MappedFile

Attributes

directories[R]
packages[R]

Public Class Methods

new() click to toggle source
# File lib/importmap/map.rb, line 8
def initialize
  @packages, @directories = {}, {}
end

Public Instance Methods

cache_sweeper(watches: nil) click to toggle source

Returns an instance ActiveSupport::EventedFileUpdateChecker configured to clear the cache of the map when the directories passed on initialization via ‘watches:` have changes. This is used in development and test to ensure the map caches are reset when javascript files are changed.

# File lib/importmap/map.rb, line 74
def cache_sweeper(watches: nil)
  if watches
    @cache_sweeper =
      Rails.application.config.file_watcher.new([], Array(watches).collect { |dir| [ dir.to_s, "js"] }.to_h) do
        clear_cache
      end
  else
    @cache_sweeper
  end
end
digest(resolver:) click to toggle source

Returns a SHA1 digest of the import map json that can be used as a part of a page etag to ensure that a html cache is invalidated when the import map is changed.

Example:

class ApplicationController < ActionController::Base
  etag { Rails.application.importmap.digest(resolver: helpers) if request.format&.html? }
end
# File lib/importmap/map.rb, line 67
def digest(resolver:)
  Digest::SHA1.hexdigest(to_json(resolver: resolver).to_s)
end
draw(path = nil, &block) click to toggle source
# File lib/importmap/map.rb, line 12
def draw(path = nil, &block)
  if path && File.exist?(path)
    begin
      instance_eval(File.read(path), path.to_s)
    rescue StandardError => e
      Rails.logger.error "Unable to parse import map from #{path}: #{e.message}"
      raise InvalidFile, "Unable to parse import map from #{path}: #{e.message}"
    end
  elsif block_given?
    instance_eval(&block)
  end

  self
end
pin(name, to: nil, preload: false) click to toggle source
# File lib/importmap/map.rb, line 27
def pin(name, to: nil, preload: false)
  clear_cache
  @packages[name] = MappedFile.new(name: name, path: to || "#{name}.js", preload: preload)
end
pin_all_from(dir, under: nil, to: nil, preload: false) click to toggle source
# File lib/importmap/map.rb, line 32
def pin_all_from(dir, under: nil, to: nil, preload: false)
  clear_cache
  @directories[dir] = MappedDir.new(dir: dir, under: under, path: to, preload: preload)
end
preloaded_module_paths(resolver:, cache_key: :preloaded_module_paths) click to toggle source

Returns an array of all the resolved module paths of the pinned packages. The ‘resolver` must respond to `path_to_asset`, such as `ActionController::Base.helpers` or `ApplicationController.helpers`. You’ll want to use the resolver that has been configured for the ‘asset_host` you want these resolved paths to use. In case you need to resolve for different asset hosts, you can pass in a custom `cache_key` to vary the cache used by this method for the different cases.

# File lib/importmap/map.rb, line 42
def preloaded_module_paths(resolver:, cache_key: :preloaded_module_paths)
  cache_as(cache_key) do
    resolve_asset_paths(expanded_preloading_packages_and_directories, resolver: resolver).values
  end
end
to_json(resolver:, cache_key: :json) click to toggle source

Returns a JSON hash (as a string) of all the resolved module paths of the pinned packages in the import map format. The ‘resolver` must respond to `path_to_asset`, such as `ActionController::Base.helpers` or `ApplicationController.helpers`. You’ll want to use the resolver that has been configured for the ‘asset_host` you want these resolved paths to use. In case you need to resolve for different asset hosts, you can pass in a custom `cache_key` to vary the cache used by this method for the different cases.

# File lib/importmap/map.rb, line 53
def to_json(resolver:, cache_key: :json)
  cache_as(cache_key) do
    JSON.pretty_generate({ "imports" => resolve_asset_paths(expanded_packages_and_directories, resolver: resolver) })
  end
end

Private Instance Methods

absolute_root_of(path) click to toggle source
# File lib/importmap/map.rb, line 163
def absolute_root_of(path)
  (pathname = Pathname.new(path)).absolute? ? pathname : Rails.root.join(path)
end
cache_as(name) { || ... } click to toggle source
# File lib/importmap/map.rb, line 89
def cache_as(name)
  if result = instance_variable_get("@cached_#{name}")
    result
  else
    remember_cache_key(name)
    instance_variable_set("@cached_#{name}", yield)
  end
end
clear_cache() click to toggle source
# File lib/importmap/map.rb, line 104
def clear_cache
  @cache_keys&.each do |name|
    instance_variable_set("@cached_#{name}", nil)
  end
end
expand_directories_into(paths) click to toggle source
# File lib/importmap/map.rb, line 137
def expand_directories_into(paths)
  @directories.values.each do |mapping|
    if (absolute_path = absolute_root_of(mapping.dir)).exist?
      find_javascript_files_in_tree(absolute_path).each do |filename|
        module_filename = filename.relative_path_from(absolute_path)
        module_name     = module_name_from(module_filename, mapping)
        module_path     = module_path_from(module_filename, mapping)

        paths[module_name] = MappedFile.new(name: module_name, path: module_path, preload: mapping.preload)
      end
    end
  end
end
expanded_packages_and_directories() click to toggle source
# File lib/importmap/map.rb, line 133
def expanded_packages_and_directories
  @packages.dup.tap { |expanded| expand_directories_into expanded }
end
expanded_preloading_packages_and_directories() click to toggle source
# File lib/importmap/map.rb, line 129
def expanded_preloading_packages_and_directories
  expanded_packages_and_directories.select { |name, mapping| mapping.preload }
end
find_javascript_files_in_tree(path) click to toggle source
# File lib/importmap/map.rb, line 159
def find_javascript_files_in_tree(path)
  Dir[path.join("**/*.js{,m}")].collect { |file| Pathname.new(file) }.select(&:file?)
end
module_name_from(filename, mapping) click to toggle source
# File lib/importmap/map.rb, line 151
def module_name_from(filename, mapping)
  [ mapping.under, filename.to_s.remove(filename.extname).remove(/\/?index$/).presence ].compact.join("/")
end
module_path_from(filename, mapping) click to toggle source
# File lib/importmap/map.rb, line 155
def module_path_from(filename, mapping)
  [ mapping.path || mapping.under, filename.to_s ].compact.join("/")
end
remember_cache_key(name) click to toggle source
# File lib/importmap/map.rb, line 98
def remember_cache_key(name)
  @cache_keys ||= Set.new
  @cache_keys.add name
end
rescuable_asset_error?(error) click to toggle source
# File lib/importmap/map.rb, line 110
def rescuable_asset_error?(error)
  Rails.application.config.importmap.rescuable_asset_errors.any? { |e| error.is_a?(e) }
end
resolve_asset_paths(paths, resolver:) click to toggle source
# File lib/importmap/map.rb, line 114
def resolve_asset_paths(paths, resolver:)
  paths.transform_values do |mapping|
    begin
      resolver.path_to_asset(mapping.path)
    rescue => e
      if rescuable_asset_error?(e)
        Rails.logger.warn "Importmap skipped missing path: #{mapping.path}"
        nil
      else
        raise e
      end
    end
  end.compact
end