UtopiaSourceUtopiaImportMap

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 base String, nil

The base URI for resolving relative paths.

yields {|builder| ...}

If a block is given.

parameter builder Builder

The import map builder, if the block takes an argument.

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 imports Hash

The imports mapping.

parameter integrity Hash

Integrity hashes for imports.

parameter scopes Hash

Scoped import mappings.

parameter base String, 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 specifier String

The import specifier (e.g., "react", "@myapp/utils").

parameter value String

The URL or path to resolve to.

parameter integrity String, 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_prefix String

The scope prefix (e.g., "/pages/").

parameter imports Hash

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 path String

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