Search code examples
ruby-on-railsimport-mapsruby-on-rails-8

Can I use the importmap-rails gem to replace yarn or can I use it with yarn?


I'm having trouble using importmap-rails to manage npm packages. I would ideally like it to function like yarn where it keeps track of versions, dependencies and downloads everything for you with a command like yarn install.

But it seems like importmap-rails is only set up to use a cdn when you pin something. Is this true? Or can it download packages and their dependencies with a command like yarn?

If it can't do that, can I use importmap-rails with yarn as my package manager? I tried this with underscore.js and ran into some issues.

  1. Ran yarn add underscore, saw it was downloaded correctly and then added node_modules to the asset paths so that propshaft and importmap-rails would know to look there:
config/application.rb
config.assets.paths << Rails.root.join('node_modules')

Then pinned the underscore folder:

# config/importmap.rb
pin_all_from 'node_modules/underscore', under: 'underscore'

Then imported the functionality I needed:

// from a custom js file
import debounce from 'underscore/modules/debounce';

Running ./bin/importmap json showed that the debounce.js file was found:

{
  "imports": {
    "application": "/assets/application-bd82d6ab.js",
    "underscore/amd/_baseCreate": "/assets/underscore/amd/_baseCreate-7eccab16.js",
    "underscore/amd/_baseIteratee": "/assets/underscore/amd/_baseIteratee-5f781255.js",
    "underscore/amd/_cb": "/assets/underscore/amd/_cb-dd403ec6.js",
    "underscore/amd/_chainResult": "/assets/underscore/amd/_chainResult-4b98e265.js",
    "underscore/amd/_collectNonEnumProps": "/assets/underscore/amd/_collectNonEnumProps-ed4c206f.
...
    "underscore/modules/debounce": "/assets/underscore/modules/debounce-decaec4e.js",

And in the browser I didn't get any errors for debounce.js, it was fingerprinted and found, but inside of that file it was using relative paths to import other functions which caused 404 errors. Also there was a get request going to every single file in underscore, which is also not good.

15:41:34 web.1  | Completed 200 OK in 5482ms (Views: 2431.0ms | ActiveRecord: 557.1ms (24 queries, 6 cached) | GC: 368.1ms)
15:41:34 web.1  | 
15:41:34 web.1  | 
15:41:34 web.1  | Started GET "/assets/underscore/amd/_baseCreate-7eccab16.js" for ::1 at 2024-12-20 15:41:34 -0500
15:41:35 web.1  | Started GET "/assets/underscore/amd/_baseIteratee-5f781255.js" for ::1 at 2024-12-20 15:41:35 -0500
15:41:35 web.1  | Started GET "/assets/underscore/amd/_cb-dd403ec6.js" for ::1 at 2024-12-20 15:41:35 -0500
15:41:35 web.1  | Started GET "/assets/underscore/amd/_chainResult-4b98e265.js" for ::1 at 2024-12-20 15:41:35 -0500
15:41:35 web.1  | Started GET "/assets/underscore/amd/_collectNonEnumProps-ed4c206f.js" for ::1 at 2024-12-20 15:41:35 -0500
15:41:35 web.1  | Started GET "/assets/underscore/amd/_createAssigner-aa588b74.js" for ::1 at 2024-12-20 15:41:35 -0500
15:41:36 web.1  | Started GET "/assets/underscore/amd/_createEscaper-3ea6026c.js" for ::1 at 2024-12-20 15:41:36 -0500
15:41:36 web.1  | Started GET "/assets/underscore/amd/_createPredicateIndexFinder-f577fa32.js" for ::1 at 2024-12-20 15:41:36 -0500
...

debounce-decaec4e.js:1 
GET http://localhost:3000/assets/underscore/modules/restArguments.js 404 (Not Found)

debounce-decaec4e.js:2 
GET http://localhost:3000/assets/underscore/modules/now.js net::ERR_ABORTED 404 (Not Found)
debounce-decaec4e.js
import restArguments from './restArguments.js';
import now from './now.js';

So, can I use importmap-rails with yarn or instead of yarn for npm packages?

I've googled around and combed the documentation and there is only this brief description: https://github.com/rails/importmap-rails/blob/main/README.md#using-npm-packages-via-javascript-cdns

For extra context I just updated our rails 7 app to rails 8, so we just went from sprockets, yarn, webpacker to propshaft with importmap-rails. Happy to be without webpacker, but missing the yarn functionality.


Solution

  • You can use yarn to get the packages but you have to use ES module package:

    pin "underscore", to: "underscore/underscore-esm.js"
    
    import { debounce } from "underscore"
    

    If you need just one function:

    pin "/assets/underscore/modules/debounce.js", to: "underscore/modules/debounce.js"
    # debounce dependencies
    pin "/assets/underscore/modules/now.js", to: "underscore/modules/now.js"
    pin "/assets/underscore/modules/restArguments.js", to: "underscore/modules/restArguments.js"
    

    which will allow for relative imports:

    // app/javascript/application.js
    
    // relative to the asset url
    import debounce from "./underscore/modules/debounce.js"
    

    To clarify the relative part, imports are relative to the url, and you can use ./ and ../ as needed to match the importmap, given you have the correct pin setup:

    application                                          => import
    application                                          => matches importmap
    /assets/application-c10b436c.js                      => OK
            ⌄
          ./underscore/modules/debounce.js               => import
    /assets/underscore/modules/debounce.js               => resolve relative url
    /assets/underscore/modules/debounce.js               => matches importmap
    /assets/underscore/modules/debounce-1fc53fe7.js      => OK
                               ⌄
                             ./restArguments.js          => import
    /assets/underscore/modules/restArguments.js          => resolve relative url
    /assets/underscore/modules/restArguments.js          => matches importmap
    /assets/underscore/modules/restArguments-708796bd.js => OK