Class: Dommy::Js::ModuleLoader

Inherits:
Object
  • Object
show all
Defined in:
lib/dommy/js/module_loader.rb

Overview

The ESM resolver the engine consults for every import. Resolves a specifier to an absolute URL (bare via the import map, relative against the importer, absolute as-is), then fetches its source through the Resources interface. CSS imports become an empty module (apps that import "./x.css" for side effects don't crash). Returns the engine contract: { code:, as: } | nil (nil → module resolution error).

Constant Summary collapse

CSS_STUB =

An empty default export so import sheet from "./x.css" yields an object rather than failing — layout/styling is out of scope.

"export default {};"

Instance Method Summary collapse

Constructor Details

#initialize(resources, import_map, base_url: nil) ⇒ ModuleLoader

Returns a new instance of ModuleLoader.



18
19
20
21
22
23
# File 'lib/dommy/js/module_loader.rb', line 18

def initialize(resources, import_map, base_url: nil)
  @resources = resources
  @import_map = import_map
  @base_url = base_url.to_s
  @seeded = {}
end

Instance Method Details

#call(specifier, importer = nil) ⇒ Object

The engine module-loader callable.



55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/dommy/js/module_loader.rb', line 55

def call(specifier, importer = nil)
  url = resolve_url(specifier, importer)
  return nil unless url

  return {code: @seeded[url], as: url} if @seeded.key?(url)
  return {code: CSS_STUB, as: url} if css?(url)

  response = @resources&.get(url)
  return nil unless response&.success?

  {code: response.body, as: url}
end

#resolve_url(specifier, importer = nil) ⇒ Object

Resolve a specifier to an absolute URL string (nil if unresolvable). Bare specifiers go through the import map; relative/absolute resolve against the importer (or the document base URL for the entry module).



71
72
73
74
75
76
77
78
79
80
81
# File 'lib/dommy/js/module_loader.rb', line 71

def resolve_url(specifier, importer = nil)
  spec = specifier.to_s
  base = importer.to_s.empty? ? @base_url : importer.to_s

  if relative?(spec) || absolute?(spec) || spec.start_with?("/")
    join(base, spec)
  else
    mapped = @import_map&.resolve(spec, importer)
    mapped ? join(base, mapped) : nil
  end
end

#seed(url, source) ⇒ Object

Register an in-memory module source under url (served before any network fetch). Used to give an inline <script type="module"> a real document-based URL as its module identity, so its relative imports resolve against the page instead of a synthetic filename.



29
30
31
32
# File 'lib/dommy/js/module_loader.rb', line 29

def seed(url, source)
  @seeded[url.to_s] = source.to_s
  url.to_s
end

#seed_inline(preferred_url, source) ⇒ Object

Seed an inline module under preferred_url (the document URL) if free, so the common single-inline-module page gets a clean import.meta.url == the page URL. A second inline module on the same page falls back to a #dommy-inline-N fragment for a unique module identity (the engine caches modules by canonical name); the fragment is ignored when resolving relative imports, so ./x still resolves against the page. Returns the chosen URL.



41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/dommy/js/module_loader.rb', line 41

def seed_inline(preferred_url, source)
  base = preferred_url.to_s
  base = "about:blank" if base.empty?
  url = base
  i = 0
  while @seeded.key?(url)
    i += 1
    url = "#{base}#dommy-inline-#{i}"
  end
  @seeded[url] = source.to_s
  url
end