javascriptruby-on-railswebpack-2

How to use jquery from views on rails 5.1.3 + webpacker


I want to use a simple jQuery function like so:

$( document ).ready(function(){
    $('body').addClass("faded");
  });

From a single view in a rails 5.1.3 app, in this case 'login.html.erb'

On webpack's application.js I have:

var $ = require('jquery');

which makes the aforementioned jquery call work IF I put it on the same application.js file but this would mean it would be called for all the pages since <%= javascript_pack_tag 'application' %> is on the layout.

When I try to run it on login.html.erb like so:

<script type="text/javascript">

$( document ).ready(function(){
    $('body').addClass("faded");
  });
</script>

I get an error on console: "ReferenceError: $ is not defined".

If I try to do " import $ from 'jquery'; " on the login.html.erb file I get this error instead: "SyntaxError: import declarations may only appear at top level of a module" which understandably means that my local .erb view doesn't get access to the javascript referenced on webpacker's application.js and cant import it from there.

How can I reference jquery and for that matter any module being served by webpacker, from the views?

I apologize if this has been asked before, I'm posting this question after days of reading about webpack, the webpacker gem and javascript without finding a solution.

:)


Solution

  • First things first. You cannot transpile es6 in erb templates within script tags with webpacker.

    Since introduction of assets pipeline in rails the general idea is to deliver bundles of multiple javascript files. Prior to webpacker by defining sprockets manifests and now with packs. This allows to save http requests for the js assets files. The browser loads only one file with all of the javascript on the first http request to the app and caches it for further page requests.

    Thus you can define separate js files for the parts of your application and import the all in the application.js pack. Only pack files can be inlcuded via a javascript_include_tag. You must also import jquery in each imported es6 module, because es6 modules have their own variable scope. (just check the transpiled output in chrome dev tools)

    However now, you can`t just call addClass on body, because you only want it to happen on the login page. One way to solve is setting a separate class (e.g ".login") within the erb template and use it as a selector.

    //app/javascript/packs/application.js
    import 'startpage'
    import 'login'
    
    //app/javascript/src/login/index.js
    import $ from 'jquery'
    $('.login .content').addClass("faded");
    

    If faded really has to be added on the body (which is not in the template) you can either try select it via parent matcher from the ".login" class or somehow introduce a variable in the rails layout which is set from within an erb template, in order to add controller specific marker for the layout.

    Ofc you can also deliver a separate js file per page, if you think that this does not produce to many http requests and the speed is good enough for your users. Just make them all separate packfiles within packs folder.