class ImportMap
Represents an import map for JavaScript modules with support for URI and relative path resolution. Import maps allow you to control how JavaScript imports are resolved, supporting both absolute URLs and relative paths with proper context-aware resolution.
The builder pattern supports nested base URIs that are properly resolved relative to parent bases.
All URL resolution follows RFC 3986 via the protocol-url gem.
Example: Basic usage with absolute URLs.
import_map = Utopia::ImportMap.build do |map|
map.import("react", "https://esm.sh/react@18")
map.import("@myapp/utils", "./js/utils.js", integrity: "sha384-...")
end
puts import_map.to_html
Example: Using nested base URIs for different CDNs.
import_map = Utopia::ImportMap.build do |map|
# Imports without base
map.import("app", "/app.js")
# CDN imports - base is set to jsdelivr
map.with(base: "https://cdn.jsdelivr.net/npm/") do |m|
m.import "lit", "lit@2.7.5/index.js"
m.import "lit/decorators.js", "lit@2.7.5/decorators.js"
end
# Nested base combines with parent: "https://cdn.jsdelivr.net/npm/mermaid@10/"
map.with(base: "https://cdn.jsdelivr.net/npm/") do |m|
m.with(base: "mermaid@10/") do |nested|
nested.import "mermaid", "dist/mermaid.esm.min.mjs"
end
end
end
Example: Creating page-specific import maps with relative paths.
# Global import map with base: "/_components/"
global_map = Utopia::ImportMap.build(base: "/_components/") do |map|
map.import("button", "./button.js")
end
# For a page at /foo/bar/, create a context-specific import map
page_map = global_map.relative_to("/foo/bar/")
# Base becomes: "../../_components/"
# button import resolves to: "../../_components/button.js"
puts page_map.to_html
Signature
Nested
Definitions
def self.build(base: nil, &block)
Create an import map using a builder pattern.
The builder supports both block parameter and instance_eval styles. The returned import map is frozen to prevent accidental mutation.
Example: Block parameter style.
import_map = ImportMap.build do |map|
map.import("react", "https://esm.sh/react")
end
Example: Instance eval style.
import_map = ImportMap.build do
import "react", "https://esm.sh/react"
end
Signature
-
parameter
baseString, nil The base URI for resolving relative paths.
-
yields
{|builder| ...} If a block is given.
-
parameter
builderBuilder The import map builder, if the block takes an argument.
-
parameter
-
returns
ImportMap A frozen import map instance.
Implementation
def self.build(base: nil, &block)
instance = self.new(base: base)
builder = Builder.build(instance, &block)
return instance.freeze
end
def initialize(imports = {}, integrity = {}, scopes = {}, base: nil)
Initialize a new import map.
Typically you should use Utopia::ImportMap.build instead of calling this directly.
Signature
-
parameter
importsHash The imports mapping.
-
parameter
integrityHash Integrity hashes for imports.
-
parameter
scopesHash Scoped import mappings.
-
parameter
baseString, Protocol::URL, nil The base URI for resolving relative paths.
Implementation
def initialize(imports = {}, integrity = {}, scopes = {}, base: nil)
@imports = imports
@integrity = integrity
@scopes = scopes
@base = Protocol::URL[base]
end
attr :imports
Signature
-
attribute
Hash(String, String) The imports mapping.
attr :integrity
Signature
-
attribute
Hash(String, String) Subresource integrity hashes for imports.
attr :scopes
Signature
-
attribute
Hash(String, Hash) Scoped import mappings.
attr :base
Signature
-
attribute
Protocol::URL::Absolute | Protocol::URL::Relative | nil The parsed base URL for efficient resolution.
def import(specifier, value, integrity: nil)
Add an import mapping.
Signature
-
parameter
specifierString The import specifier (e.g., "react", "@myapp/utils").
-
parameter
valueString The URL or path to resolve to.
-
parameter
integrityString, nil Optional subresource integrity hash for the resource.
-
returns
ImportMap Self for method chaining.
Implementation
def import(specifier, value, integrity: nil)
@imports[specifier] = value
@integrity[specifier] = integrity if integrity
self
end
def scope(scope_prefix, imports)
Add a scope mapping.
Scopes allow different import resolutions based on the referrer URL. See https://github.com/WICG/import-maps#scoping-examples for details.
Signature
-
parameter
scope_prefixString The scope prefix (e.g., "/pages/").
-
parameter
importsHash Import mappings specific to this scope.
-
returns
ImportMap Self for method chaining.
Implementation
def scope(scope_prefix, imports)
@scopes[scope_prefix] = imports
self
end
def relative_to(path)
Create a new import map with paths relative to the given page path. This is useful for creating page-specific import maps from a global one.
Example: Creating page-specific import maps.
# Global import map with base: "/_components/"
import_map = ImportMap.build(base: "/_components/") { ... }
# For a page at /foo/bar/, calculate relative path to components
page_map = import_map.relative_to("/foo/bar/")
# Base becomes: "../../_components/"
Signature
-
parameter
pathString The absolute page path to make imports relative to.
-
returns
ImportMap A new import map with a relative base.
Implementation
def relative_to(path)
if @base
# Calculate the relative path from the page to the base
relative_base = Protocol::URL::Path.relative(@base.path, path)
resolved_base = Protocol::URL[relative_base]
else
resolved_base = nil
end
instance = self.class.new(@imports.dup, @integrity.dup, @scopes.dup, base: resolved_base)
return instance.freeze
end
def as_json(...)
Build the import map as a Hash with resolved paths.
All relative paths are resolved against the base URL if present.
Absolute URLs and protocol-relative URLs are preserved as-is.
This method is compatible with the JSON gem's as_json convention.
Signature
-
returns
Hash The resolved import map data structure ready for JSON serialization.
Implementation
def as_json(...)
result = {}
# Add imports
if @imports.any?
result["imports"] = resolve_imports(@imports, @base)
end
# Add scopes
if @scopes.any?
result["scopes"] = {}
@scopes.each do |scope_prefix, scope_imports|
# Resolve the scope prefix itself with base
scope_url = Protocol::URL[scope_prefix]
resolved_prefix = if @base && !scope_url.is_a?(Protocol::URL::Absolute)
(@base + scope_url).to_s
else
scope_prefix
end
result["scopes"][resolved_prefix] = resolve_imports(scope_imports, @base)
end
end
# Add integrity
if @integrity.any?
result["integrity"] = @integrity.dup
end
return result
end
def to_json(...)
Convert the import map to JSON.
Signature
-
returns
String The JSON representation of the import map.
Implementation
def to_json(...)
as_json.to_json(...)
end
def to_html
Generate the import map as an XRB fragment suitable for embedding in HTML.
Creates a <script type="importmap"> tag containing the JSON representation.
Signature
-
returns
XRB::Builder::Fragment The generated HTML fragment.
Implementation
def to_html
json_data = to_json
XRB::Builder.fragment do |builder|
builder.inline("script", type: "importmap") do
builder.raw(json_data)
end
end
end
def to_s
Convenience method for rendering the import map as an HTML string.
Equivalent to to_html.to_s.
Signature
-
returns
String The generated HTML containing the import map script tag.
Implementation
def to_s
to_html.to_s
end