Using Rails-UJS in JS modules (Rails 6 with webpacker)
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;
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
-
For loaders webpacker configures itself, it'll dump a config object in
config/webpack/loaders
. Create that folder if it doesn't exist. -
Create a file called
config/webpack/loaders/expose.js
-
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.
R4ttlesnake
Updated on June 03, 2022Comments
-
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 callingRails.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 almost 5 yearsI've done exactly that. Webpack cries
TS2304: Cannot find name 'Rails'
. -
Andrew Cetinic almost 5 yearsDid you also add it via package.json ? npm add @rails/ujs
-
RWDJ almost 5 yearsI apologize. Let me clarify. I can import the package, but using the global variable
Rails
givesTS2304: Cannot find name 'Rails'
. -
RWDJ almost 5 yearsHowever, it worked for JQuery, so thank you anyways. Just not Rails.
-
R4ttlesnake over 4 yearsThanks 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 doingimport Rails from "@rails/ujs"
several times across different controllers? If not, i'd mark this one as accepted answer. -
inopinatus over 4 yearsNo 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 ofRails
, and this remains true even if you import it with a different name, or where there's a CommonJSrequire()
rather than ES6import
. Take care to only call.start()
once, probably in yourapplication.js
. -
ThienSuBS over 4 yearsPlease see it to get Rails instance in javascript. stackoverflow.com/a/58161486/4631412
-
rico_mac over 4 years@ThienSuBS this is the one - I spent weeks looking for the answer. Big thanks
-
Dan L over 4 yearsI 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 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 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.