In Rails 6, JavaScript assets are no longer included with the app/assets/
directory and instead have been moved into a separate directory app/javascript
handled by Webpacker. That's great, but when I wanted to add some custom javascript of my own, there wasn't any clear documentation that described how it should be done.
After some experimentation and digging around on the internet, here are a couple of methods that seem to work. Note that I'm not a javascript expert by any means, so if there is a better way—or other ways that I'm missing—let me know in the comments!
Method 1: Create a custom directory and require it in application.js
If you look at application.js
, the comment at the top suggests that you "place your actual application logic in a relevant structure within app/javascript and only use these pack files to reference that code so it'll be compiled."
// app/javascript/packs/application.js
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
You can set this up by adding a custom directory within the app/javascript/
, e.g. custom/
, and then require the files within in application.js
.
For example, if you have a javascript file named home.js
in the app/javascript/custom/
directory, you can get it to load with require()
:
// app/javascript/packs/application.js
// ...
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require("custom/home")
Since it's required in application.js
, the custom javascript will be compiled along with all the other javascript into a timestamped application.js
file that looks something like application-a03d1868c0a3f825e53e.js
.
This is loaded by the <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
tag in the app/views/layouts/application.html.erb
, automatically created when you make a new Rails app.
For a cleaner look, you could also name the javascript file index.js
and require it with a simple reference to the directory like require("custom")
because require()
looks for an index.js
file when given a path to a directory (if there is no index.js
, it will fail).
Method 2: Add custom JavaScipt within the app/javascript/packs
directory
If for some reason you don't want to create another directory, you could opt to add custom javascript files within the app/javascript/packs
directory. Then, require the files in application.js
with require()
.
For example, if you have a file named custom.js
in app/javascript/packs
, require it like below and it will be compiled into the timestamped application.js
file with all the other javascript:
// app/javascript/packs/application.js
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require("packs/custom")
Method 3: Using javascript_pack_tag
for separate JavaScript files (packs)
If you don't want your custom javascript to be compiled into the application.js
with everything else, you can get Rails to compile it into a separate file, or "pack."
To do so, add a custom file within app/javascript/packs
, e.g. custom.js
, then use the javascript_pack_tag
helper where the file is needed in the views like so: <%= javascript_pack_tag 'custom' %>
The custom javascript will be compiled separately from the rest of the javascript into a timestamped custom.js
that looks something like this: custom-a03d1756c0a3f825e53e.js
Here's how it might look if you added the custom javascript just before the ending body
tag in the layouts/views/application.html.erb
:
# app/layouts/views/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>
<body class="<%= controller_name %> <%= action_name %>">
<%= render 'layouts/header' %>
<%= yield %>
<%= render 'layouts/footer' %>
<%= javascript_pack_tag 'custom' %>
</body>
</html>
Thanks for reading!
Top comments (19)
Method 2 is horrible. I tried is personally and its hard to believe that JS files are not compiled according to the order given but with alphabetical naming of those files.
so in case your file requires jquery and has name custom.js, with following order:
require("jquery")
require("packs/custom")
It first compiles custom.js then jquery.
Have you found any method for including js from node_modules in order? Or is the replacement for the asset pipeline just useless in Rails 6 because we can't define the order in which js is included?
Why you have to include? Just use yarn add package_name to add js library and require or import the global variable.
I meant require when I said include. The webpack puts the JS in the wrong order and it doesn't work when I do a regular require.
Thanks for the article! I try to call JavaScript 'alert' when clicking the button:
Well, I did some digging... Since these are webpack modules, I guess you have to do a little more work to make the function available if you're simply declaring it in
pack/custom.js
. First, you need to export function by adding anexport
at the end of the file:Then, in the main
application.js
, you have to import it and make it available to thewindow
. You can do that by adding this to the bottom ofapplication.js
Then it should work! I think there's a way to export and import all functions in a file too... I'm not very familiar with how webpack works yet, but I'll try to update this post with some more info soon.
Instead of exporting/importing, another way is to add the onClick function with an event listener instead. First, give your button and id or something like that and remove the
onclick
:Then in the
custom.js
file, add an event listener:You do need to make sure you wrap the event listener (and other JS) inside the
document.addEventListener('turbolinks:load', () => {}
. That's Rails' way of making sure the page and turbolinks is loaded before any of the javascript gets executed!I hope that helps!! I learned a bit trying to figure this out too!
Thanks for this article. It was helpful. Changing the declaration of your JavaScript function may simplify things a bit:
// in your html
<a href="#" onclick="dosmth()">dosmth</a>
// javascript/packs/custom.js
window.dosmth = function() {
alert('hello');
}
// I don't think you need the export default dosmth;
// export default dosmth;
// In your javascript/packs/application.js I don't think you need the corresponding declarations:
// import dosmth from './custom'
// window.dosmth = dosmth
Ooh, great thanks, Felice!
Thanks for the article. Any way to add coffee files? I tried
require("custom/pagination")
andrequire("custom/pagination.coffee")
but I get the following error: Error: Cannot find module 'custom/pagination.coffee'`try
require("./custom/pagination")
Apparently Coffee scripts are not supported by Rails 6.
Awesome post! I ended up using a variation of the method 3:
Using
include_javascript_pack 'my_module'
in anindex.html.slim
view file.Creating a
app/javascript/my_module/index.js
file with the actual JS code.Creating a
app/javascript/packs/my_module.js
pack that imports the JS code:import 'my_module';
.Then I ran:
RAILS_ENV=development bundle exec rails webpacker:compile
.Thanks for this article @morinoko! Just started up a Rails 6 application and couldn't figure out where to put the JavaScript files. Now I know thanks to this article.
Thanks again for reading :D Good luck with your new application!
I am currently working on Action Singal-ing the connection for a webchat. Credits to Jean Paul Sio's work. His worked for in Rails 5 but Rails 6 is a new different ballgame. Two functions have been getting reference errors namely ()handleJoinSession and ()handleLeaveSession
**in the packs/signaling-server.js these two functions are syntaxed this way:
const handleJoinSession = async () => {
App.session = await App.cable.subscriptions.create("SessionChannel", {
connected: () => {
broadcastData({
type: JOIN_ROOM,
from: currentUser
});
},
received: data => {
console.log("received", data);
if (data.from === currentUser) return;
switch (data.type) {
case JOIN_ROOM:
return joinRoom(data);
case EXCHANGE:
if (data.to !== currentUser) return;
return exchange(data);
case REMOVE_USER:
return removeUser(data);
default:
return;
}
}
});
};
const handleLeaveSession = () => {
for (user in pcPeers) {
pcPeers[user].close();
}
pcPeers = {};
App.session.unsubscribe();
remoteVideoContainer.innerHTML = "";
broadcastData({
type: REMOVE_USER,
from: currentUser
});
};
const joinRoom = data => {
createPC(data.from, true);
};
const removeUser = data => {
console.log("removing user", data.from);
let video = document.getElementById(
remoteVideoContainer+${data.from}
);video && video.remove();
delete pcPeers[data.from];
};
While in the views section they are called through a button click
like wise with
I was able to access camera and have something pop out by simply adding
import handleJoinSession from './signaling-server' or require("packs/signaling-server") in the application.js (following your advise). I can also make the camera work just by the javascript tag in the views but still the reference error occurs. The perfect scenario would be these function be referenced so that the camera would turn on as with the handleJoinSession and off with the handleLeaveSession occurs. Anyone well versed with the javascript syntax, I would like to thank in advance. :)
Here is the exact error: (index):1 Uncaught ReferenceError: handleLeaveSession is not defined
at HTMLButtonElement.onclick (VM73 :1)
I'm currently learning ruby on rails and this post is really helpful. Thank you so much. I have a question, where is the best place to put the js link tag?
Currently, I put it in application.html.erb
<%= javascript_include_tag "cdn.ckeditor.com/4.16.1/full/ckedi..." %>
But I think there's still a better place to put it
Thanks in advance. :)
Method 3 worked perfectly for me. Thanks.
Thanks for the succinct article, Felice! I used Method 3 for some simple menu show/hide functionality.
how to add bundle install gems to application.js file since some gems for available on yarn. in rails 6