13

Just replaced the js pipeline in my Rails app with webpacker.

Most things work correctly, however controllers that render js no longer work as expected.

def action
  format.js { render "javascript_partial" }
end

Normally, the above would execute a file in my view called javascript_partial.js.erb or action.js.erb if not specified in render.

The problem seems to be that these files have no connection to the webpacker pipeline and thus cannot access global libraries like jquery or explicitly manage their own imports.

This code now causes client-side syntax errors because it cannot access the jquery $ function:

$("#element").html(<= j render partial: 'partial', locals: { object: @object } %>

I have a related problem with in-line js in my views. Something like the following,

<%= form.collection_select ... onchange: 'Rails.fire(this.form, "submit")' %>

no longer works, because in-line js cannot access global objects such as Rails.

This seems to be a straightforward problem but I cannot find documentation anywhere.

Does anyone how to harmonize webpacker with historically expected Rails/js behavior? Do I need to bring back sprockets?


If it helps, my javascript/packs/application.js file looks something like,

import Rails from 'rails-ujs';
import Turbolinks from 'turbolinks';

Rails.start();
Turbolinks.start();

$(document).on("turbolinks:load", () => {
  // initial setup ...
});

The above works perfectly fine, and has access to jquery because I've exported it in config/webpack/environment.js,

environment.plugins.append('Provide', new webpack.ProvidePlugin({
  $: 'jquery',
  jQuery: 'jquery',
  jquery: 'jQuery'
}));

2 Answers 2

14

Figured it out thanks to this wonderful article!

Use expose-loader to expose key libraries to vanilla JavaScript sprinkled throughout your app.

1) Install dependency,

yarn add expose-loader --dev

2) Configure config/webpack/environment.js,

const { environment } = require('@rails/webpacker');

environment.config.merge({
  module: {
    rules: [
      {
        test: require.resolve('jquery'),
        use: [{
          loader: 'expose-loader',
          options: '$'
        }, {
          loader: 'expose-loader',
          options: 'jQuery'
        }]
      },
      {
        test: require.resolve('rails-ujs'),
        use: [{
          loader: 'expose-loader',
          options: 'Rails'
        }]
      }
    ]
  }
});

module.exports = environment;
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you very much for coming back and answering this. Just a note, I had to use require.resolve('@rails/ujs') for my namespace, but very obvious error. This answer was the only thing that actually solved the problem.. I just needed my remote js templates to have access to $. That's it. I'm trying to get ahead by ripping out sprockets now, but it was embarrassing how hard it was to figure out how to expose jquery, just for one liners of stupid dom selectors (i know i know, i learned my lesson) Anyways, i was about 5 minutes away from git reset --harding this branch, so beers on me!
@RyanRomanchuk so glad it helped! Let me know how it goes ripping out sprockets. I tried removing the entire Rails asset pipeline but couldn't get my app to function normally or deploy to Heroku. GOOD LUCK.
This made me laugh in 2022 because i'm 100% back on sprockets. I love import-maps, but at this rate, i'll just check back in two years from now.
2

I had a similar problem where I had undefined $ on the string $("#element").html(insert_code)

My configuration is similar to yours I solved by exposing at the end of the file pack/application.js

window.jQuery = jQuery
window.$ = $

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.