Using Rails-UJS in JS modules (Rails 6 with webpacker)

15,735

Solution 1

in my app/javascript/packs/application.js:

import Rails from '@rails/ujs';
Rails.start();

and then in whatever module, controller, component I'm writing:

import Rails from '@rails/ujs';

Solution 2

First at all, using yarn add rails/ujs:

yarn add  @rails/ujs

And add to config/webpack/environment.js

const webpack = require('webpack')
environment.plugins.prepend('Provide',
  new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery',
    Popper: ['popper.js', 'default'],
    toastr: 'toastr/toastr',
    ApexCharts: ['apexcharts', 'default'],
    underscore: ['underscore', 'm'],
    Rails: ['@rails/ujs']
  })
)
module.exports = environment

Config and load Rails js.

# pack/application.js
require("@rails/ujs").start()
global.Rails = Rails;

And Then: This is result -> My result when i typed Rails in Firefox Console

Solution 3

Just add it to your environment.js file, here is mine (with bootstrap and jquery):

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

module.exports = environment

environment.plugins.prepend(
    'Provide',
    new webpack.ProvidePlugin({
        $: 'jquery',
        jQuery: 'jquery',
        jquery: 'jquery',
        'window.jQuery': 'jquery',
        "window.$": "jquery",
        Popper: ['popper.js', 'default'],
        Rails: ['@rails/ujs']
    })
)

Solution 4

I am currently messing around on 6.0.0.rc2 but I think I got an answer for you.

So if you separate out the:

app/javascript/packs/application.js

require("@rails/ujs").start()
import "controllers"

To instead:

export const rails_ujs = require("@rails/ujs")
console.log(rails_ujs)
rails_ujs.start()

You can obviously remove that console.log was just trying to figure things out. Then in your stimulus controller you can simply do:

// Visit The Stimulus Handbook for more details
// https://stimulusjs.org/handbook/introduction
//
// This example controller works with specially annotated HTML like:
//
// <div data-controller="hello">
//   <h1 data-target="hello.output"></h1>
// </div>

import { Controller } from "stimulus"
import { rails_ujs } from "packs/application.js"

export default class extends Controller {
  static targets = [ "output" ]

  connect() {
    // this.outputTarget.textContent = 'Hello, Stimulus!'
    console.log('hi')
    console.log(rails_ujs)
  }
}

Just using their little test controller here but I got it to console.log out and you can call rails_ujs.fire so that should be what you want :)

Let me know if this works for you!

Solution 5

I think the best way is to use the expose-loader and configure it the same way webpacker would if you ran bundle exec rails webpacker:install:erb.


Install the expose-loader

$ yarn add expose-loader

Create a config file

  1. For loaders webpacker configures itself, it'll dump a config object in config/webpack/loaders. Create that folder if it doesn't exist.

  2. Create a file called config/webpack/loaders/expose.js

  3. Add this to that file:

    module.exports = {
      test: require.resolve('@rails/ujs'),
      use: [{
        loader: 'expose-loader',
        options: 'Rails'
       }]
    }
    
    // later versions of expose loader may allow the following API:
    module.exports = {
      test: require.resolve('@rails/ujs'),
      loader: 'expose-loader',
      options: {exposes: "Rails"}
    }
    

Add that loader to environment.js

Add these two lines to config/webpack/environment.js:

const expose = require('./loaders/expose')
environment.loaders.prepend('expose', expose)

The full file should look something like:

const { environment } = require('@rails/webpacker')
const expose = require('./loaders/expose')

environment.loaders.prepend('expose', expose)
module.exports = environment

That should give you access to the Rails object globally again.

Share:
15,735
R4ttlesnake
Author by

R4ttlesnake

Updated on June 03, 2022

Comments

  • R4ttlesnake
    R4ttlesnake almost 2 years

    i just switched to Rails 6 (6.0.0.rc1) which uses the Webpacker gem by default for Javascript assets together with Rails-UJS. I want to use Rails UJS in some of my modules in order to submit forms from a function with:

    const form = document.querySelector("form")
    Rails.fire(form, "submit")
    

    In former Rails versions with Webpacker installed, the Rails reference seemed to be "globally" available in my modules, but now i get this when calling Rails.fire

    ReferenceError: Rails is not defined
    

    How can i make Rails from @rails/ujs available to a specific or to all of my modules?

    Below my setup…

    app/javascript/controllers/form_controller.js

    import { Controller } from "stimulus"
    
    export default class extends Controller {
      // ...
      submit() {
        const form = this.element
        Rails.fire(form, "submit")
      }
      // ...
    }
    

    app/javascript/controllers.js

    // Load all the controllers within this directory and all subdirectories. 
    // Controller files must be named *_controller.js.
    
    import { Application } from "stimulus"
    import { definitionsFromContext } from "stimulus/webpack-helpers"
    
    const application = Application.start()
    const context = require.context("controllers", true, /_controller\.js$/)
    application.load(definitionsFromContext(context))
    

    app/javascript/packs/application.js

    require("@rails/ujs").start()
    import "controllers"
    

    Thanks!

  • RWDJ
    RWDJ almost 5 years
    I've done exactly that. Webpack cries TS2304: Cannot find name 'Rails'.
  • Andrew Cetinic
    Andrew Cetinic almost 5 years
    Did you also add it via package.json ? npm add @rails/ujs
  • RWDJ
    RWDJ almost 5 years
    I apologize. Let me clarify. I can import the package, but using the global variable Rails gives TS2304: Cannot find name 'Rails'.
  • RWDJ
    RWDJ almost 5 years
    However, it worked for JQuery, so thank you anyways. Just not Rails.
  • R4ttlesnake
    R4ttlesnake over 4 years
    Thanks for the answer, it works simple and well for me. I am now importing the form_controller.js form a npm package in order to reuse it across several rails applications. Is there any downside in doing import Rails from "@rails/ujs" several times across different controllers? If not, i'd mark this one as accepted answer.
  • inopinatus
    inopinatus over 4 years
    No downside - in fact, I'd say it's exactly how we're supposed to declare each file's dependencies in the modular ES6 world. Webpack will load @rails/ujs exactly once, no matter how many times it's imported. Each controller simply gets the same export of Rails, and this remains true even if you import it with a different name, or where there's a CommonJS require() rather than ES6 import. Take care to only call .start() once, probably in your application.js.
  • ThienSuBS
    ThienSuBS over 4 years
    Please see it to get Rails instance in javascript. stackoverflow.com/a/58161486/4631412
  • rico_mac
    rico_mac over 4 years
    @ThienSuBS this is the one - I spent weeks looking for the answer. Big thanks
  • Dan L
    Dan L over 4 years
    I would see @ThienSuBS's answer and read the docs here: webpack.js.org/plugins/provide-plugin . I had the same problem; I didn't want to import Rails in every file I used it, and the webpack plugin support is built exactly for that situation: "Automatically load modules instead of having to import or require them everywhere."
  • inopinatus
    inopinatus over 4 years
    @Dan L: depending on globals is one of the most notorious programming anti-patterns, so no, I don't endorse the ProvidePlugin, and instead specifically do endorse importing dependencies. Writing modules that depend on global runtime state is how people ended up in this schmozzle to begin with. All the more so given that the question is about writing modules. Modules that depend on global state aren't ... modular.
  • Dan L
    Dan L over 4 years
    @inopinatus: I understand the concern with globals everywhere and generally agree. I'm ok with Rails specifically in the global space because it's a utility library that abstracts common features (like AJAX requests) that I want to use all the time. I view it as similar to jQuery; I don't want to import jQuery on every single JS file, because jQuery is a tool that I'm using to build my real application code. That said, there's a lot of wisdom in avoiding globals like you mentioned.