importmap-rails 0.3.4 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 29a1e2ec6414bcdbd4b6cd4c9726c838ea2f6b0179797f35c23ed0e9769495c1
4
- data.tar.gz: 760b8781cf2c0a60ee4603440f183e6786c3147f537000b5ff0dd653fef3c754
3
+ metadata.gz: 8e3127a8154739158fd8b7feea069b5ae6e8d353d36ba16fa446095f26b70ae0
4
+ data.tar.gz: c6d7e65df38a98e5c31b5195ee298651c6550cb97e3c438ebf46c8a5290a17b6
5
5
  SHA512:
6
- metadata.gz: 7a30aba12a32f3399b6d6e1c2853012866ba9720cf49e8ec4306932f3a0835478aac88a014e95bfda052d7415e5f1a37afb34404edbfd8b8df00e0aa6ffcfd1b
7
- data.tar.gz: fba823c13bd453195561a892ac36e4855fb4d147b4c5b14099e6ad840b282184927e0496e89506d536e9af56eb93a8e3b267e83a699d46f4f76427230f9c51be
6
+ metadata.gz: 2e186a8e9785fc7da247ddf1c459d005977a8ce21fb2403aa3e3b63c2b938b9ffa682df1742292633dd9afcae0f7a7f68582af2cafc642afd04195564fd5c2a1
7
+ data.tar.gz: fc85e86b9d747e9e84e8e009bcca8e4746f6477f25d6355b705958fb917818872005127c5ad9ac8e9f9f7594d1ac06b68013038ab351cd072d45c0d4dc76b7ce
data/README.md CHANGED
@@ -22,30 +22,49 @@ Note: In order to use JavaScript from Rails frameworks like Action Cable, Action
22
22
 
23
23
  The import map is configured programmatically through the `Rails.application.config.importmap` assignment, which by default is setup in `config/importmap.rb` after running the installer. This file is automatically reloaded in development upon changes, but note that you must restart the server if you remove pins and need them gone from the rendered importmap or list of preloads.
24
24
 
25
- This programmatically configured import map is inlined in the `<head>` of your application layout using `<%= javascript_importmap_tags %>`, which will setup the JSON configuration inside a `<script type="importmap">` tag. After that, the [es-module-shim](https://github.com/guybedford/es-module-shims) is loaded, and then finally the application entrypoint is imported via `<script type="module">import "application"</script>`. That logical entrypoint, `application`, is mapped in the importmap script tag to the file `app/assets/javascripts/application.js`, which is copied and digested by the asset pipeline.
25
+ This programmatically configured import map is inlined in the `<head>` of your application layout using `<%= javascript_importmap_tags %>`, which will setup the JSON configuration inside a `<script type="importmap">` tag. After that, the [es-module-shim](https://github.com/guybedford/es-module-shims) is loaded, and then finally the application entrypoint is imported via `<script type="module">import "application"</script>`. That logical entrypoint, `application`, is mapped in the importmap script tag to the file `app/javascript/application.js`, which is copied and digested by the asset pipeline.
26
26
 
27
- It's in `app/assets/javascripts/application.js` you setup your application by importing any of the modules that have been defined in the import map. You can use the full ESM functionality of importing any particular export of the modules or everything.
27
+ It's in `app/javascript/application.js` you setup your application by importing any of the modules that have been defined in the import map. You can use the full ESM functionality of importing any particular export of the modules or everything.
28
28
 
29
29
  It makes sense to use logical names that match the package names used by NPM, such that if you later want to start transpiling or bundling your code, you'll not have to change any module imports.
30
30
 
31
31
 
32
- ## Use with Skypack (and other CDNs)
32
+ ## Using node modules via JavaScript CDNs
33
33
 
34
- Instead of mapping JavaScript modules to files in your application's path, you can also reference them directly from JavaScript CDNs like Skypack. Simply add them to the `config/importmap.rb` with the URL instead of the local path:
34
+ Importmap for Rails is designed to be used with JavaScript CDNs for your node package dependencies. The CDNs provide pre-compiled distribution versions ready to use, and offer a fast, efficient way of serving them.
35
35
 
36
- ```ruby
37
- Rails.application.config.importmap.draw do
38
- pin "trix", to: "https://cdn.skypack.dev/trix"
39
- pin "md5", to: "https://cdn.skypack.dev/md5"
40
- end
36
+ You can use the bin/importmap command that's added as part of the install to pin additional packages to your importmap. This command uses an API from JSPM.org to resolve your package dependencies most efficiently, and then add the pins to your config/importmap.rb file. It can resolve these dependencies from JSPM itself, but also from other CDNs, like unpkg.com, jsdelivr.com, skypack.dev, etc.
37
+
38
+ It works like so:
39
+
40
+ ```bash
41
+ ./bin/importmap pin react react-dom
42
+
43
+ Pinned 'react' to https://ga.jspm.io/npm:react@17.0.2/index.js
44
+ Pinned 'react-dom' to https://ga.jspm.io/npm:react-dom@17.0.2/index.js
45
+ Pinned 'object-assign' to https://ga.jspm.io/npm:object-assign@4.1.1/index.js
46
+ Pinned 'scheduler' to https://ga.jspm.io/npm:scheduler@0.20.2/index.js
47
+
48
+ ./bin/importmap json
49
+
50
+ {
51
+ "imports": {
52
+ "application": "/application.js",
53
+ "react": "https://ga.jspm.io/npm:react@17.0.2/index.js",
54
+ "react-dom": "https://ga.jspm.io/npm:react-dom@17.0.2/index.js",
55
+ "object-assign": "https://ga.jspm.io/npm:object-assign@4.1.1/index.js",
56
+ "scheduler": "https://ga.jspm.io/npm:scheduler@0.20.2/index.js"
57
+ }
58
+ }
41
59
  ```
42
60
 
61
+ As you can see, the two packages react and react-dom resolve to a total of four dependencies, when resolved via the jspm default.
62
+
43
63
  Now you can use these in your application.js entrypoint like you would any other module:
44
64
 
45
65
  ```js
46
- import "trix"
47
- import md5 from "md5"
48
- console.log(md5("Hash it out"))
66
+ import React from "react"
67
+ import ReactDOM from "react-dom"
49
68
  ```
50
69
 
51
70
 
@@ -57,16 +76,14 @@ Example:
57
76
 
58
77
  ```ruby
59
78
  # config/importmap.rb
60
- Rails.application.config.importmap.draw do
61
- pin "trix", to: "https://cdn.skypack.dev/trix"
62
- pin "md5", to: "https://cdn.skypack.dev/md5", preload: false
63
- end
79
+ pin "@github/hotkey", to: "https://ga.jspm.io/npm:@github/hotkey@1.4.4/dist/index.js"
80
+ pin "md5", to: "https://cdn.jsdelivr.net/npm/md5@2.3.0/md5.js", preload: false
64
81
 
65
82
  # app/views/layouts/application.html.erb
66
83
  <%= javascript_importmap_tags %>
67
84
 
68
85
  # will include the following link before the importmap is setup:
69
- <link rel="modulepreload" href="https://cdn.skypack.dev/trix">
86
+ <link rel="modulepreload" href="https://ga.jspm.io/npm:@github/hotkey@1.4.4/dist/index.js">
70
87
  ...
71
88
  ```
72
89
 
@@ -0,0 +1,24 @@
1
+ require "thor"
2
+ require "importmap/pinner"
3
+
4
+ class Importmap::Commands < Thor
5
+ desc "pin [*PACKAGES]", "Pin new packages"
6
+ option :env, type: :string, aliases: :e, default: "production"
7
+ option :from, type: :string, aliases: :f, default: "jspm"
8
+ def pin(*packages)
9
+ if imports = Importmap::Pinner.new.pin(*packages, env: options[:env], from: options[:from])
10
+ imports.each do |package, url|
11
+ puts "Pinned '#{package}' to #{url}"
12
+ end
13
+ else
14
+ puts "Couldn't find any packages in #{packages.inspect} on #{options[:provider]}"
15
+ end
16
+ end
17
+
18
+ desc "json", "Show the full importmap in json"
19
+ def json
20
+ puts Rails.application.config.importmap.to_json(resolver: ActionController::Base.helpers)
21
+ end
22
+ end
23
+
24
+ Importmap::Commands.start(ARGV)
@@ -2,19 +2,16 @@ require "importmap/map"
2
2
 
3
3
  module Importmap
4
4
  class Engine < ::Rails::Engine
5
- config.importmap = Importmap::Map.new
6
-
5
+ config.importmap = Importmap::Map.new.draw("config/importmap.rb")
7
6
  config.autoload_once_paths = %W( #{root}/app/helpers )
8
7
 
9
8
  initializer "importmap.reloader" do |app|
10
9
  app.config.paths.add "config/importmap.rb"
11
10
 
12
- reloader = Importmap::Reloader.new
13
-
14
- reloader.execute
15
- app.reloaders << reloader
16
- app.reloader.to_run do
11
+ Importmap::Reloader.new.tap do |reloader|
17
12
  reloader.execute
13
+ app.reloaders << reloader
14
+ app.reloader.to_run { reloader.execute }
18
15
  end
19
16
  end
20
17
 
data/lib/importmap/map.rb CHANGED
@@ -1,17 +1,30 @@
1
+ require "pathname"
2
+
1
3
  class Importmap::Map
2
- attr_reader :files, :directories
4
+ attr_reader :packages, :directories
3
5
  attr_accessor :cached
4
6
 
5
7
  def initialize
6
- @files, @directories = {}, {}
8
+ @packages, @directories = {}, {}
7
9
  end
8
10
 
9
- def draw(&block)
10
- instance_eval(&block)
11
+ def draw(path = nil, &block)
12
+ if path
13
+ begin
14
+ instance_eval(File.read(path))
15
+ rescue Exception => e
16
+ Rails.logger.error "Unable to parse import map from #{path}: #{e.message}"
17
+ raise "Unable to parse import map from #{path}: #{e.message}"
18
+ end
19
+ else
20
+ instance_eval(&block)
21
+ end
22
+
23
+ self
11
24
  end
12
25
 
13
26
  def pin(name, to: nil, preload: true)
14
- @files[name] = MappedFile.new(name: name, path: to || "#{name}.js", preload: preload)
27
+ @packages[name] = MappedFile.new(name: name, path: to || "#{name}.js", preload: preload)
15
28
  end
16
29
 
17
30
  def pin_all_from(dir, under: nil, to: nil, preload: true)
@@ -20,19 +33,19 @@ class Importmap::Map
20
33
 
21
34
  def preloaded_module_paths(resolver:)
22
35
  cache_as(:preloaded_module_paths) do
23
- resolve_asset_paths(expanded_preloading_files_and_directories, resolver: resolver).values
36
+ resolve_asset_paths(expanded_preloading_packages_and_directories, resolver: resolver).values
24
37
  end
25
38
  end
26
39
 
27
40
  def to_json(resolver:)
28
41
  cache_as(:json) do
29
- { "imports" => resolve_asset_paths(expanded_files_and_directories, resolver: resolver) }.to_json
42
+ JSON.pretty_generate({ "imports" => resolve_asset_paths(expanded_packages_and_directories, resolver: resolver) })
30
43
  end
31
44
  end
32
45
 
33
46
  private
34
- MappedFile = Struct.new(:name, :path, :preload, keyword_init: true)
35
47
  MappedDir = Struct.new(:dir, :path, :under, :preload, keyword_init: true)
48
+ MappedFile = Struct.new(:name, :path, :preload, keyword_init: true)
36
49
 
37
50
  def cache_as(name)
38
51
  if (cached && result = instance_variable_get("@cached_#{name}"))
@@ -53,12 +66,12 @@ class Importmap::Map
53
66
  end.compact
54
67
  end
55
68
 
56
- def expanded_preloading_files_and_directories
57
- expanded_files_and_directories.select { |name, mapping| mapping.preload }
69
+ def expanded_preloading_packages_and_directories
70
+ expanded_packages_and_directories.select { |name, mapping| mapping.preload }
58
71
  end
59
72
 
60
- def expanded_files_and_directories
61
- @files.dup.tap { |expanded| expand_directories_into expanded }
73
+ def expanded_packages_and_directories
74
+ @packages.dup.tap { |expanded| expand_directories_into expanded }
62
75
  end
63
76
 
64
77
  def expand_directories_into(paths)
@@ -0,0 +1,32 @@
1
+ require "httparty"
2
+
3
+ class Importmap::Pinner
4
+ include HTTParty
5
+ base_uri "https://api.jspm.io"
6
+
7
+ def pin(*packages, env: "production", from: "jspm")
8
+ fetch_imports(*packages, env: env, provider: from)&.tap do |imports|
9
+ imports.each do |package, url|
10
+ append_to_importmap package, url
11
+ end
12
+ end
13
+ end
14
+
15
+ private
16
+ def append_to_importmap(package, url)
17
+ Rails.root.join("config/importmap.rb").open("a") do |config|
18
+ config.puts %(pin "#{package}", to: "#{url}")
19
+ end
20
+ end
21
+
22
+ def fetch_imports(*packages, env:, provider:)
23
+ response = self.class.post("/generate", body: {
24
+ "install" => Array(packages),
25
+ "flattenScope" => true,
26
+ "env" => [ "browser", env ],
27
+ "provider" => provider.to_s
28
+ }.to_json)
29
+
30
+ response.dig("map", "imports")
31
+ end
32
+ end
@@ -2,7 +2,7 @@ class Importmap::Reloader
2
2
  delegate :execute_if_updated, :execute, :updated?, to: :updater
3
3
 
4
4
  def reload!
5
- import_map_paths.each { |path| load path }
5
+ import_map_paths.each { |path| config.importmap.draw(path) }
6
6
  end
7
7
 
8
8
  private
@@ -1,3 +1,3 @@
1
1
  module Importmap
2
- VERSION = "0.3.4"
2
+ VERSION = "0.4.0"
3
3
  end
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../config/application"
4
+ require "importmap/commands"
@@ -0,0 +1,20 @@
1
+ # Use Action Cable channels (remember to import "@rails/actionable" in your application.js)
2
+ # pin "@rails/actioncable", to: "actioncable.esm.js"
3
+ # pin_all_from "app/javascript/channels", under: "channels"
4
+
5
+ # Use direct uploads for Active Storage (remember to import "@rails/activestorage" in your application.js)
6
+ # pin "@rails/activestorage", to: "activestorage.esm.js"
7
+
8
+ # Pin vendored modules by first adding the following to app/assets/config/manifest.js:
9
+ # //= link_tree ../../../vendor/assets/javascripts .js
10
+ # pin_all_from "vendor/assets/javascripts"
11
+
12
+ # JavaScript CDN provider. Also available: :jsdelivr, :esmsh, :unpkg, :skypack
13
+ provider :jspm
14
+
15
+ # Use NPM libraries from CDN
16
+ # pin "local-time", version: "2.1.0", file: "app/assets/javascripts/local-time.js"
17
+ # pin "vue", version: "2.6.14", file: "dist/vue.esm.browser.js", from: :jsdelivr
18
+ # pin "d3", version: "7.0.1", file: "?bundle", from: :esmsh
19
+
20
+ pin "application"
@@ -10,10 +10,7 @@ end
10
10
 
11
11
  say "Create application.js module as entrypoint"
12
12
  create_file Rails.root.join("app/javascript/application.js") do <<-JS
13
- // Configure your import map in config/importmap.rb
14
-
15
- // import "@rails/actioncable"
16
- // import "@rails/activestorage"
13
+ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
17
14
  JS
18
15
  end
19
16
 
@@ -21,21 +18,8 @@ say "Ensure JavaScript files are in the asset pipeline manifest"
21
18
  append_to_file Rails.root.join("app/assets/config/manifest.js"), %(//= link_tree ../../javascript .js\n)
22
19
 
23
20
  say "Configure importmap paths in config/importmap.rb"
24
- create_file Rails.root.join("config/importmap.rb") do <<-RUBY
25
- Rails.application.config.importmap.draw do
26
- pin "application"
27
-
28
- # Use libraries available via the asset pipeline (locally or via gems).
29
- # pin "@rails/actioncable", to: "actioncable.esm.js"
30
- # pin "@rails/activestorage", to: "activestorage.esm.js"
31
-
32
- # Use libraries directly from JavaScript CDNs (see https://www.skypack.dev, https://esm.sh, https://www.jsdelivr.com/esm)
33
- # pin "vue", to: "https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.esm.browser.js"
34
- # pin "d3", to: "https://esm.sh/d3?bundle"
21
+ copy_file "#{__dir__}/config/importmap.rb", "config/importmap.rb"
35
22
 
36
- # Pin vendored modules by first adding the following to app/assets/config/manifest.js:
37
- # //= link_tree ../../../vendor/assets/javascripts .js
38
- # pin_all_from "vendor/assets/javascripts"
39
- end
40
- RUBY
41
- end
23
+ say "Copying binstub"
24
+ copy_file "#{__dir__}/bin/importmap", "bin/importmap"
25
+ chmod "bin", 0755 & ~File.umask, verbose: false
@@ -3,4 +3,10 @@ namespace :importmap do
3
3
  task :install do
4
4
  system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{File.expand_path("../install/install.rb", __dir__)}"
5
5
  end
6
+
7
+ desc "Show the importmap"
8
+ task :pins do
9
+ require Rails.root.join("config/environment")
10
+ puts Rails.application.config.importmap.to_json(resolver: ActionController::Base.helpers)
11
+ end
6
12
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: importmap-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-25 00:00:00.000000000 Z
11
+ date: 2021-08-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -24,7 +24,21 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 6.0.0
27
- description:
27
+ - !ruby/object:Gem::Dependency
28
+ name: httparty
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0.16'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0.16'
41
+ description:
28
42
  email: david@loudthinking.com
29
43
  executables: []
30
44
  extensions: []
@@ -37,10 +51,14 @@ files:
37
51
  - app/assets/javascripts/es-module-shims@0.12.8.js
38
52
  - app/helpers/importmap/importmap_tags_helper.rb
39
53
  - lib/importmap-rails.rb
54
+ - lib/importmap/commands.rb
40
55
  - lib/importmap/engine.rb
41
56
  - lib/importmap/map.rb
57
+ - lib/importmap/pinner.rb
42
58
  - lib/importmap/reloader.rb
43
59
  - lib/importmap/version.rb
60
+ - lib/install/bin/importmap
61
+ - lib/install/config/importmap.rb
44
62
  - lib/install/install.rb
45
63
  - lib/shim.js
46
64
  - lib/tasks/importmap_tasks.rake
@@ -50,7 +68,7 @@ licenses:
50
68
  metadata:
51
69
  homepage_uri: https://github.com/rails/importmap-rails
52
70
  source_code_uri: https://github.com/rails/importmap-rails
53
- post_install_message:
71
+ post_install_message:
54
72
  rdoc_options: []
55
73
  require_paths:
56
74
  - lib
@@ -66,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
84
  version: '0'
67
85
  requirements: []
68
86
  rubygems_version: 3.1.4
69
- signing_key:
87
+ signing_key:
70
88
  specification_version: 4
71
89
  summary: Use ESM with importmap to manage modern JavaScript in Rails without transpiling
72
90
  or bundling.