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", "[email protected]/index.js"
		m.import "lit/decorators.js", "[email protected]/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.



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

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.



242
243
244
# File 'lib/utopia/import_map.rb', line 242

def base
  @base
end

#importsObject (readonly)

Returns the value of attribute imports.



233
234
235
# File 'lib/utopia/import_map.rb', line 233

def imports
  @imports
end

#integrityObject (readonly)

Returns the value of attribute integrity.



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

def integrity
  @integrity
end

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



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

attr :scopes

#scopesObject (readonly)

Returns the value of attribute scopes.



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

def scopes
  @scopes
end

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



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

attr :integrity

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



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

attr :imports

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



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

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


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

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

.load_manifest(root) ⇒ Object

Load the import mappings from a Web Packages static projection manifest.



62
63
64
65
66
67
68
69
70
# File 'lib/utopia/import_map.rb', line 62

def self.load_manifest(root)
	manifest = Web::Packages::Manifest.load(root)
	base = Protocol::URL[manifest.data.fetch("base")]
	imports = manifest.import_map.fetch("imports").transform_values do |value|
		Protocol::URL[value].relative_to(base).to_s
	end
	
	return self.new(imports, base: base).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.



329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# File 'lib/utopia/import_map.rb', line 329

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.



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

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


284
285
286
287
288
289
290
# File 'lib/utopia/import_map.rb', line 284

def relative_to(path)
	resolved_base = @base&.relative_to(path)
	
	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.



265
266
267
268
269
# File 'lib/utopia/import_map.rb', line 265

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.



373
374
375
376
377
378
379
380
381
# File 'lib/utopia/import_map.rb', line 373

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.



364
365
366
# File 'lib/utopia/import_map.rb', line 364

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.



388
389
390
# File 'lib/utopia/import_map.rb', line 388

def to_s
	to_html.to_s
end