Class: Wabi::ZagVendor

Inherits:
Object
  • Object
show all
Defined in:
lib/wabi/zag_vendor.rb

Overview

Walks the jsDelivr ‘+esm` import graph for one or more root packages and rewrites cross-package CDN references (`/npm/<pkg>@<ver>/+esm`) to bare specifiers (`<pkg>`), so every file can be served locally and resolved through the importmap (Propshaft-digest-safe). Pure: HTTP is injected via `fetcher`, a callable taking a URL string and returning the body string.

Defined Under Namespace

Classes: Result

Constant Summary collapse

CDN =
"https://cdn.jsdelivr.net"
REF =

/npm/<pkg>@<ver>/+esm — <pkg> may be scoped (@scope/name). jsDelivr references both the package root (‘@ver/+esm`) and subpath exports (`@ver/dom/+esm`, e.g. @floating-ui/utils/dom); group 3 captures the optional subpath so those resolve to a distinct local module too.

%r{/npm/((?:@[^/]+/)?[^/@]+)@([^/]+?)((?:/[^/]+)*?)/\+esm}

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(fetcher) ⇒ ZagVendor

Returns a new instance of ZagVendor.



26
27
28
# File 'lib/wabi/zag_vendor.rb', line 26

def initialize(fetcher)
  @fetcher = fetcher
end

Class Method Details

.call(roots, fetcher:) ⇒ Object



22
23
24
# File 'lib/wabi/zag_vendor.rb', line 22

def self.call(roots, fetcher:)
  new(fetcher).call(roots)
end

Instance Method Details

#call(roots) ⇒ Object

roots: [{ pkg: “@zag-js/dialog”, ver: “1.41.0” }, …]



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/wabi/zag_vendor.rb', line 31

def call(roots)
  files = {}
  versions = {} # specifier => normalized version (first seen)
  queue = roots.map { |r| { spec: r[:pkg], sub: "", ver: normalize_version(r[:ver]) } }

  until queue.empty?
    item = queue.shift
    spec = item[:spec] # bare specifier incl. any subpath, e.g. "@floating-ui/utils/dom"
    pkg  = spec.sub(item[:sub], "") # package name without the subpath, for the fetch URL
    ver  = item[:ver]

    # Dedupe by SPECIFIER (package + subpath), not by URL: jsDelivr's real
    # graph references the same module both as a resolved pin (`@0.2.11`) and
    # as a range (`@%5E0.2.11`, i.e. URL-encoded `^0.2.11`). Those normalize
    # to the same version and the rewrite drops the version anyway (bare
    # specifier). Only genuinely-different versions (after normalizing the
    # range/encoding) are a real conflict a single bare importmap pin can't
    # carry.
    if versions.key?(spec)
      if versions[spec] != ver
        raise Wabi::Error,
          "vendor: #{spec} appears at two versions (#{versions[spec]} and #{ver}); " \
          "a bare importmap pin can't carry two versions"
      end
      next
    end
    versions[spec] = ver

    content = @fetcher.call("#{CDN}/npm/#{pkg}@#{ver}#{item[:sub]}/+esm")
    files[spec] = content.gsub(REF) do
      dep_pkg = Regexp.last_match(1)
      dep_ver = normalize_version(Regexp.last_match(2))
      dep_sub = Regexp.last_match(3).to_s # "" or "/dom"
      dep_spec = "#{dep_pkg}#{dep_sub}"
      queue << { spec: dep_spec, sub: dep_sub, ver: dep_ver }
      dep_spec
    end
  end

  Result.new(files, versions)
end