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.



203
204
205
206
207
208
# File 'lib/utopia/import_map.rb', line 203

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.



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

def base
  @base
end

#importsObject (readonly)

Returns the value of attribute imports.



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

def imports
  @imports
end

#integrityObject (readonly)

Returns the value of attribute integrity.



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

def integrity
  @integrity
end

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



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

attr :scopes

#scopesObject (readonly)

Returns the value of attribute scopes.



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

def scopes
  @scopes
end

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



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

attr :integrity

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



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

attr :imports

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



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

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


187
188
189
190
191
192
193
# File 'lib/utopia/import_map.rb', line 187

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.



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/utopia/import_map.rb', line 311

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.



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

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


262
263
264
265
266
267
268
269
270
271
272
273
274
# File 'lib/utopia/import_map.rb', line 262

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

#scope(scope_prefix, imports) ⇒ Object

Add a scope mapping.

Scopes allow different import resolutions based on the referrer URL. See github.com/WICG/import-maps#scoping-examples for details.



243
244
245
246
247
# File 'lib/utopia/import_map.rb', line 243

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.



355
356
357
358
359
360
361
362
363
# File 'lib/utopia/import_map.rb', line 355

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.



346
347
348
# File 'lib/utopia/import_map.rb', line 346

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`.



370
371
372
# File 'lib/utopia/import_map.rb', line 370

def to_s
  to_html.to_s
end