Class: Utopia::ImportMap

Inherits:
Object
  • Object
show all
Defined in:
lib/utopia/import_map.rb

Overview

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.

Examples:

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

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

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

Defined Under Namespace

Classes: Builder

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(imports = {}, integrity = {}, scopes = {}, base: nil) ⇒ ImportMap

Initialize a new import map.

Typically you should use build instead of calling this directly.



211
212
213
214
215
216
# File 'lib/utopia/import_map.rb', line 211

def initialize(imports = {}, integrity = {}, scopes = {}, base: nil)
	@imports = imports
	@integrity = integrity
	@scopes = scopes
	@base = Protocol::URL[base]
end

Instance Attribute Details

#baseObject (readonly)

Returns the value of attribute base.



228
229
230
# File 'lib/utopia/import_map.rb', line 228

def base
  @base
end

#importsObject (readonly)

Returns the value of attribute imports.



219
220
221
# File 'lib/utopia/import_map.rb', line 219

def imports
  @imports
end

#integrityObject (readonly)

Returns the value of attribute integrity.



222
223
224
# File 'lib/utopia/import_map.rb', line 222

def integrity
  @integrity
end

#Scoped import mappings.(importmappings.) ⇒ Object (readonly)



225
# File 'lib/utopia/import_map.rb', line 225

attr :scopes

#scopesObject (readonly)

Returns the value of attribute scopes.



225
226
227
# File 'lib/utopia/import_map.rb', line 225

def scopes
  @scopes
end

#Subresource integrity hashes for imports.(integrityhashes) ⇒ Object (readonly)



222
# File 'lib/utopia/import_map.rb', line 222

attr :integrity

#The imports mapping.(importsmapping.) ⇒ Object (readonly)



219
# File 'lib/utopia/import_map.rb', line 219

attr :imports

#The parsed base URL for efficient resolution.(parsedbaseURL) ⇒ Object (readonly)



228
# File 'lib/utopia/import_map.rb', line 228

attr :base

Class Method Details

.build(base: nil, &block) ⇒ Object

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.

Examples:

Block parameter style.

import_map = ImportMap.build do |map|
	map.import("react", "https://esm.sh/react")
end

Instance eval style.

import_map = ImportMap.build do
	import "react", "https://esm.sh/react"
end


195
196
197
198
199
200
201
# File 'lib/utopia/import_map.rb', line 195

def self.build(base: nil, &block)
	instance = self.new(base: base)
	
	builder = Builder.build(instance, &block)
	
	return instance.freeze
end

Instance Method Details

#as_jsonObject

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.



319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'lib/utopia/import_map.rb', line 319

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

#import(specifier, value, integrity: nil) ⇒ Object

Add an import mapping.



236
237
238
239
240
241
# File 'lib/utopia/import_map.rb', line 236

def import(specifier, value, integrity: nil)
	@imports[specifier] = value
	@integrity[specifier] = integrity if integrity
	
	self
end

#relative_to(path) ⇒ Object

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.

Examples:

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/"


270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/utopia/import_map.rb', line 270

def relative_to(path)
	if @base
		# Calculate the relative path from the page to the base
		relative_path = @base.path.relative(path)
		resolved_base = Protocol::URL::Relative.new(relative_path)
	else
		resolved_base = nil
	end
	
	instance = self.class.new(@imports.dup, @integrity.dup, @scopes.dup, base: resolved_base)
	
	return instance.freeze
end

#scope(scope_prefix, imports) ⇒ Object

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.



251
252
253
254
255
# File 'lib/utopia/import_map.rb', line 251

def scope(scope_prefix, imports)
	@scopes[scope_prefix] = imports
	
	self
end

#to_htmlObject

Generate the import map as an XRB fragment suitable for embedding in HTML.

Creates a <script type="importmap"> tag containing the JSON representation.



363
364
365
366
367
368
369
370
371
# File 'lib/utopia/import_map.rb', line 363

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

#to_jsonObject

Convert the import map to JSON.



354
355
356
# File 'lib/utopia/import_map.rb', line 354

def to_json(...)
	as_json.to_json(...)
end

#to_sObject

Convenience method for rendering the import map as an HTML string.

Equivalent to to_html.to_s.



378
379
380
# File 'lib/utopia/import_map.rb', line 378

def to_s
	to_html.to_s
end