0

Installing chart.js to Rails 8 with importmap

Install package

bin/importmap pin chart.js                  
Pinning "chart.js" to vendor/javascript/chart.js.js via download from https://ga.jspm.io/npm:[email protected]/dist/chart.js
Pinning "@kurkle/color" to vendor/javascript/@kurkle/color.js via download from https://ga.jspm.io/npm:@kurkle/[email protected]/dist/color.esm.js

Got two new files:

vendor/javascript/chart.js.js<br>
vendor/javascript/@kurkle--color.js 

strange name @kurkle--color.js but ok it was automatic and look like doesn't matter

Got two new lines at the end of config/importmap.rb

pin "application"
pin "@hotwired/turbo-rails", to: "turbo.min.js"
pin "@hotwired/stimulus", to: "stimulus.min.js"
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
pin_all_from "app/javascript/controllers", under: "controllers"
pin "chart.js" # @4.5.1
pin "@kurkle/color", to: "@kurkle--color.js" # @0.3.4

Add chart in app/javascript/application.js

import "@hotwired/turbo-rails"
import "controllers"
import "chart.js"

Everything looks clear and by book. But not working and all other controllers from app/javascript/controllers stop working.

Trying another way to pin to JSPM in config/importmap.rb

pin "chart.js", to: "https://ga.jspm.io/npm:[email protected]/dist/chart.js"
pin "@kurkle/color", to: "https://ga.jspm.io/npm:@kurkle/[email protected]/dist/color.esm.js"

And it's working.

QUESTION
Why files added by standard way bin/importmap pin are not working and import map do not read from vendor/javascript

Other files related to importmap configuration

app/javascript/controllers/application.js

import { Application } from "@hotwired/stimulus"

const application = Application.start()

// Configure Stimulus development experience
application.debug = false
window.Stimulus   = application

export { application }
0

1 Answer 1

0

importmap-rails v2 is forever broken. Chart.js has a relative import which only works relative to cdn and it is not downloaded by bin/importmap:

import {...} from "../_/MwoWUuIu.js"

Using a cdn is probably the best option:

pin "chart.js", to: "https://ga.jspm.io/npm:[email protected]/dist/chart.js"
pin "@kurkle/color", to: "https://ga.jspm.io/npm:@kurkle/[email protected]/dist/color.esm.js"

Relative imports resolve correctly from a cdn:

https://ga.jspm.io/npm:[email protected]/dist/chart.js
`-> import {...} from "../_/MwoWUuIu.js
    `-> https://ga.jspm.io/npm:[email protected]/_/MwoWUuIu.js

when you download chart.js file the url and the whole path is different:

http://localhost:3000/assets/chart.js
`-> import {...} from "../_/MwoWUuIu.js
    `-> http://localhost:3000/_/MwoWUuIu.js   404

One way to solve this is to make a relative pin:

wget -P vendor/javascript/_/ https://ga.jspm.io/npm:[email protected]/_/MwoWUuIu.js

pin "./_/MwoWUuIu.js", to: "_/MwoWUuIu.js"

You can patch download option back, like in v1 where it was false by default so you don't have to pin manually:

# bin/importmap

#!/usr/bin/env ruby

require_relative "../config/application"

# Add back --download option, make it false by default
if ENV["IMPORTMAP_PATCH"] == "1"
  puts "Download option patch enabled"

  module ImportmapCommandsDownloadOptionPatch
    def self.prepended base
      base.option :download, for: :pin,   type: :boolean, aliases: :d, default: false
      base.option :download, for: :unpin, type: :boolean, aliases: :d, default: false
    end

    private def pin_package(package, url, preload)
      if options[:download]
        super
      else
        puts %(Pinning "#{package}" to #{url})
        pin = packager.pin_for(package, url)
        update_importmap_with_pin(package, pin)
      end
    end
  end

  require "thor"
  class Importmap::Commands < Thor
    def self.start(argv)
      prepend ImportmapCommandsDownloadOptionPatch
      super
    end
  end
end

require "importmap/commands"
$ IMPORTMAP_PATCH=1 bin/importmap pin chart.js

Download option patch enabled
Pinning "chart.js" to https://ga.jspm.io/npm:[email protected]/dist/chart.js
Pinning "@kurkle/color" to https://ga.jspm.io/npm:@kurkle/[email protected]/dist/color.esm.js

Alternatively, you could try downloading all the dependencies. This is a bit more involved to configure relative pins correctly, no warranty on this one:

# bin/importmap

#!/usr/bin/env ruby

require_relative "../config/application"

if ENV["IMPORTMAP_PATCH"] == "1"
  # ...
end

# Try to download all the dependecies
if ENV["IMPORTMAP_PATCH"] == "2"
  puts "Dependecy patch enabled"

  module ImportmapCommandsDependencyPatch
    def pin(*packages)
      super
      packager.extract_dependencies.each do |dep, opts|
        opts => {to:, url:}
        puts %(  Pinning dependency "#{dep}" to #{packager.vendor_path}/#{to}.js via download from #{url})
        packager.download(to, url)
        pin = packager.pin_for(dep, "#{to}.js", preloads: options[:preload])
        update_importmap_with_pin(dep, pin)
      end
    end
  end

  require "thor"
  class Importmap::Commands < Thor
    def self.start(argv)
      prepend ImportmapCommandsDependencyPatch
      super
    end
  end

  module ImportmapPackagerDependencyPatch
    private def extract_parsed_response(response)
      parsed = JSON.parse(response.body)
      imports = parsed.dig("map", "imports")
      @parsed = parsed # need to grab this
      {imports: imports}
    end

    # Take @parsed:
    #
    #   {
    #     "staticDeps" => [ "https://ga.jspm.io/npm:@kurkle/[email protected]/dist/color.esm.js", "https://ga.jspm.io/npm:[email protected]/_/MwoWUuIu.js", "https://ga.jspm.io/npm:[email protected]/dist/chart.js" ],
    #     "dynamicDeps" => [],
    #     "map" => { "imports" => { "chart.js" => "https://ga.jspm.io/npm:[email protected]/dist/chart.js", "@kurkle/color" => "https://ga.jspm.io/npm:@kurkle/[email protected]/dist/color.esm.js" } }
    #   }
    #
    # and extract staticDeps to make a pin relative to its package so we
    # can remap it to be realtive to our url:
    #
    #   {
    #     "./_/MwoWUuIu.js" => { to: "chart.js--_--MwoWUuIu", url: "https://ga.jspm.io/npm:[email protected]/_/MwoWUuIu.js" }
    #   }
    #
    # isn't it obvious :)
    def extract_dependencies
      @parsed.dig("map", "imports").each_with_object({}) do |(name, url), deps|
        relative_dir = url.match(/.+@.+?\//).to_s
        @parsed["staticDeps"].each do |dep_url|
          if dep_url.start_with?(relative_dir) && dep_url != url
            deps["./#{dep_url.delete_prefix(relative_dir)}"] = {
              to: "#{name}/#{dep_url.delete_prefix(relative_dir)}".gsub("/", "--").chomp(".js"),
              url: dep_url,
            }
          end
        end
      end
    end
  end

  require "importmap/packager"
  Importmap::Packager.prepend ImportmapPackagerDependencyPatch
end

require "importmap/commands"
$ IMPORTMAP_PATCH=2 bin/importmap pin chart.js

Dependecy patch enabled
Pinning "chart.js" to vendor/javascript/chart.js.js via download from https://ga.jspm.io/npm:[email protected]/dist/chart.js
Pinning "@kurkle/color" to vendor/javascript/@kurkle/color.js via download from https://ga.jspm.io/npm:@kurkle/[email protected]/dist/color.esm.js
  Pinning dependency "./_/MwoWUuIu.js" to vendor/javascript/chart.js--_--MwoWUuIu.js via download from https://ga.jspm.io/npm:[email protected]/_/MwoWUuIu.js

This will add the missing pin:

pin "chart.js" # @4.5.1
pin "@kurkle/color", to: "@kurkle--color.js" # @0.3.4
pin "./_/MwoWUuIu.js", to: "chart.js--_--MwoWUuIu.js"

Keep in mind, this might download a lot and is super slow. Relative pins are not unique, if it happens to be ./utils.js from two packages it would clash, the fix would be to nest all packages into their own directories instead of the current flat structure:

$ IMPORTMAP_PATCH=2 bin/importmap pin date-fns

Dependecy patch enabled
Pinning "date-fns" to vendor/javascript/date-fns.js via download from https://ga.jspm.io/npm:[email protected]/index.js
  Pinning dependency "./_/BDJn6Y19.js" to vendor/javascript/date-fns--_--BDJn6Y19.js via download from https://ga.jspm.io/npm:[email protected]/_/BDJn6Y19.js

# ... 257 LINES SKIPPED !

  Pinning dependency "./yearsToQuarters.js" to vendor/javascript/date-fns--yearsToQuarters.js via download from https://ga.jspm.io/npm:[email protected]/yearsToQuarters.js
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.