Importmap for Rails

repository·main·Indexed 22 days ago

https://github.com/rails/importmap-rails

Allows developers to build modern JavaScript applications using ES modules directly in the browser, eliminating the need for toolchains like Webpack, Yarn, or npm. It maps logical module names to versioned files via an import map. Included by default in Rails 7+, it provides tools for pinning local modules and npm packages via CDNs, configuring Subresource Integrity (SRI), and managing dependencies through a dedicated CLI.

Tokens
5.3K
Snippets
28
Records
29
Agent score
77%

What's inside importmap-rails

  1. How importmaps work

    main

    Import maps perform string substitution for "bare module specifiers". A bare module specifier is a logical name used in an import statement, such as import React from "react".

    Because the ES Module loader spec requires absolute paths, relative paths, or HTTP paths, importmap-rails maps the bare specifier to a valid URL or path via the pin method in config/importmap.rb.

    # config/importmap.rb
    pin "react", to: "https://ga.jspm.io/npm:react@17.0.2/index.js"
  2. Install Importmap for Rails

    main

    Importmap for Rails is included by default in Rails 7+ new applications. To install it manually in an existing application, run the following commands:

    1. Add the gem to your bundle.
    2. Run the installation task.

    Note: To use JavaScript from Rails frameworks like Action Cable, Action Text, and Active Storage, you must be running Rails 7.0+ as these versions provide ESM compatible builds.

    ./bin/bundle add importmap-rails
    ./bin/rails importmap:install
  3. Pin Rails framework libraries manually

    main

    If you need to manually pin the ESM-compatible versions of Rails libraries (Action Cable, Active Storage, Action Text), use the following pins in your config/importmap.rb:

    pin "@rails/actioncable", to: "actioncable.esm.js"
    pin "@rails/activestorage", to: "activestorage.esm.js"
    pin "@rails/actiontext", to: "actiontext.esm.js"
    pin "trix"
  4. Selectively import modules on specific pages

    main

    To avoid loading all JavaScript on every page, you can pin a module with preload: false and then use javascript_import_module_tag to load it only when needed.

    Important: The javascript_import_module_tag must be rendered after the main javascript_importmap_tags in your layout.

    # config/importmap.rb
    pin "checkout", preload: false
    
    # app/views/checkout/show.html.erb
    <% content_for :head do %>
      <%= javascript_import_module_tag "checkout" %>
    <% end %>
    
    # app/views/layouts/application.html.erb
    <%= javascript_importmap_tags %>
    <%= yield(:head) %>
  5. Compose multiple import maps

    main

    By default, Rails loads the import map from config/importmap.rb. If you are building a Rails Engine or want to combine multiple configuration files, you can append additional paths to Rails.application.config.importmap.paths.

    # my_engine/lib/my_engine/engine.rb
    
    module MyEngine
      class Engine < ::Rails::Engine
        initializer "my-engine.importmap", before: "importmap" do |app|
          app.config.importmap.paths << Engine.root.join("config/importmap.rb")
        end
      end
    end
    
    # my_engine/config/importmap.rb
    
    # Pin all files from the engine's asset directory
    pin_all_from File.expand_path("../app/assets/javascripts", __dir__)
  6. Support legacy browsers (e.g., Safari on iOS 15)

    main

    For browsers that do not natively support import maps, you can use es-module-shims. Insert the shim script in your layout before the javascript_importmap_tags call.

    <script async src="https://ga.jspm.io/npm:es-module-shims@1.8.2/dist/es-module-shims.js" data-turbo-track="reload"></script>
    <%= javascript_importmap_tags %>
  7. Pin local JavaScript modules

    main

    When using Propshaft, you must explicitly pin local modules located in app/javascript to make them importable. You can use pin_all_from to pin an entire directory at once.

    • pin_all_from(path, under: logical_name, to: destination)
    • If you omit the :to option, you must provide the :under option immediately after the first parameter.

    Example of pinning a directory for use in app/javascript/application.js:

    # config/importmap.rb
    pin_all_from 'app/javascript/src', under: 'src', to: 'src'

    This allows you to use: import { ExampleFunction } from 'src/example_function'

  8. Configure Subresource Integrity (SRI)

    main

    To enable Subresource Integrity (SRI) for enhanced security, you must first call enable_integrity! in config/importmap.rb. This allows the library to automatically calculate hashes for local assets and CDN-loaded packages.

    For Propshaft users: You must explicitly configure the integrity hash algorithm in config/application.rb or your environment files, otherwise integrity will be disabled by default.

    # config/application.rb
    config.assets.integrity_hash_algorithm = 'sha256' # or 'sha384', 'sha512'

    Configuration options in config/importmap.rb:

    • enable_integrity!: Enables global integrity calculation.
    • pin "name", integrity: "hash": Manually specify a hash.
    • pin "name", integrity: false: Explicitly disable integrity for a specific pin.
    • pin "name", integrity: nil: Explicitly disable integrity for a specific pin.
    • pin_all_from "path", integrity: true: Automatically calculates hashes for all files in a directory.
    # config/importmap.rb
    enable_integrity!
    
    pin "application"                                               # Auto-calculated
    pin "admin", to: "admin.js"                                     # Auto-calculated
    pin_all_from "app/javascript/controllers", under: "controllers" # Auto-calculated
    
    pin "cdn_package", integrity: "sha384-abc123..."               # Manual
    pin "no_integrity_package", integrity: false                     # Explicitly disabled
  9. Preload or defer JavaScript modules

    main

    To prevent the 'waterfall effect' where the browser loads nested imports sequentially, importmap-rails uses modulepreload links by default.

    • To disable preloading: Use preload: false in your pin definition. This is useful for large dependencies you only want to load on-demand.
    • To preload for specific entry points: Provide a string or an array of strings to the preload: option. This ensures the module is only preloaded when a specific entry point is requested via javascript_importmap_tags.
    # config/importmap.rb
    
    # Disable preloading for this module
    pin "md5", preload: false
    
    # Preload only for specific entry points
    pin "@github/hotkey", to: "@github--hotkey.js", preload: 'application'
    pin "md5", preload: ['application', 'alternate']
    
    # app/views/layouts/application.html.erb
    # Requesting 'alternate' will trigger the preload for 'md5'
    <%= javascript_importmap_tags 'alternate' %>
  10. Audit NPM packages for outdated versions or vulnerabilities

    main

    The Importmap::Npm class provides tools to audit the JavaScript packages pinned in your config/importmap.rb against the NPM registry. This is useful for identifying packages that have newer versions available or those with known security vulnerabilities.

    Key Capabilities

    • Identify Outdated Packages: Compares the versions specified in your importmap with the latest version on NPM.
    • Identify Vulnerable Packages: Uses the NPM security advisory bulk endpoint to find packages with known vulnerabilities, returning the severity and the specific vulnerable versions.

    Implementation Details

    • The auditor scans config/importmap.rb for pins using patterns like npm:, npm/, skypack.dev/, or unpkg.com/, as well as version comments (e.g., # @1.2.3).
    • It correctly handles scoped packages (e.g., @scope/package).
    • It ignores vendored packages that do not have a version explicitly specified in the importmap.
  11. Configure cache sweeping for development and test

    main

    To improve performance, importmap-rails caches the generated import map JSON and modulepreloads. In development and test, it automatically watches config/importmap.rb and app/javascript to clear this cache when changes occur.

    • Control sweeping: Use config.importmap.sweep_cache = boolean in your environment configuration.
    • Add external directories: If you pin files from outside app/javascript (e.g., in a Rails Engine), you must add those paths to app.config.importmap.cache_sweepers so the cache is cleared when they change.
    # my_engine/lib/my_engine/engine.rb
    
    module MyEngine
      class Engine < ::Rails::Engine
        initializer "my-engine.importmap", before: "importmap" do |app|
          app.config.importmap.cache_sweepers << Engine.root.join("app/assets/javascripts")
        end
      end
    end
  12. Enable Subresource Integrity (SRI) in config/importmap.rb

    main

    You can enable automatic integrity hash calculation for all pinned modules by calling enable_integrity! in your config/importmap.rb file.

    When enabled:

    • Local assets served by the Rails asset pipeline will have their integrity hashes automatically calculated (if integrity: true is set, which is the default).
    • Modules with explicit integrity strings (e.g., pin "my-pkg", integrity: "sha384-...") will use those provided values.
    • This provides Subresource Integrity (SRI) protection to ensure JavaScript modules haven't been tampered with.

    Requirements & Notes:

    • Integrity calculation is disabled by default.
    • Requires asset pipeline support (Sprockets or Propshaft 1.2+).
    • For Propshaft, you must configure config.assets.integrity_hash_algorithm.
    • External CDN packages should provide their own integrity hashes.
    # config/importmap.rb
    enable_integrity!
    
    # These will now auto-calculate integrity hashes
    pin "application"
    pin "admin", to: "admin.js"
    pin_all_from "app/javascript/lib"
    
    # Manual control still works
    pin "no_integrity", integrity: false
    pin "custom_hash", integrity: "sha384-abc123..."