问题
My question here is the same as Using Rails 3.1, where do you put your "page specific" JavaScript code?, just for Rails 6 instead of Rails 3.1.
Suppose I have some JavaScript that I want to use for my posts index page. Where do I put that file, and how do I include it?
In the previous question the answers usually utilize the Rails Asset Pipeline. However, with Rails 6, my understanding is that it uses webpacker instead of the Asset Pipeline for JavaScript files.
Note: I don't want the file to always be included. Eg. if I am on the authors index page, I don't want the JavaScript file for the posts index page to be included. Why? Imagine I have $('li').on('click', changeColor);
in the JS file for the posts index page. I probably don't want that code to run on the authors index page, but it will if the file is included. You could get around this problem by namespacing, but I think it would be cleaner (and more performant) to just not include unnecessary files.
回答1:
I'll describe a few options in order of increasing level of difficulty with regards to experience with Webpack and Webpacker.
Forget page-specific JavaScript and put everything in the
application.js
bundle. This will most definitely be the easiest approach to understand. For a small application, the tradeoff may well be worth it as having an application that's easier to maintain may outweigh added cost of learning how to best to split up your JS code with Webpack with little performance gain.Use dynamic imports to lazy load your page-specific code. In this scenario, you would still place all your JavaScript within the
application.js
dependency graph, but not all of this code would be loaded up-front. Webpack recognizes the dynamicimport()
function when it compiles your JS and will split the imported chunk out into a separate file that can be loaded on-demand in the browser using a JS framework router or a simple trigger.For example, you could have code that looks like this:
if (document.querySelectorAll(".post-index").length) { import("./post/index") // webpack will load this JS async }
- Use page-specific "packs" combined with the splitChunks configuration API. In this scenario, instead of using an
application.js
pack, you would construct one for each page you want to treat differently, e.g,posts.js
,admin.js
etc. Using the splitChunks plugin means that your bundles can properly share code. I highly recommend treading carefully with this approach until you understand how Webpack works OR be willing to go through the process of learning Webpack in choosing this path. Webpack typically works best on the assumption you use only one entry point per page, otherwise, you may end up duplicate code across bundles unless you know what you're doing.
回答2:
Let's go through the contents of this directory in a empty Rails 6 application.
▶ tree app/javascript
app/javascript
├── channels
│ ├── consumer.js
│ └── index.js
└── packs
└── application.js
2 directories, 3 files
the packs directory is significant for us so let's see what it contains.
// app/javascript/application.js
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
webpack has a concept of entry points which are the files that it looks for first when it starts compiling your JavaScript code.
Webpacker gem creates the application pack in the form of this application.js file under app/javascript/packs. If you remember the assets pipeline, this file is equivalent to the app/assets/javascripts/application.js file
Simple example
How to Add Javascript to Ruby on Rails (Adding Javascript as a Module):
create folder in Javascript path
app/javascript
ex: 'post'add your javascript files in folder like index.js
add
post
module inapp/javascript/application.js
->require('post')
require("@rails/ujs").start() require("turbolinks").start() require("@rails/activestorage").start() require("channels") require("post")
Let's go through the contents of this directory in a Rails 6 application after add post module
▶ tree app/javascript
app/javascript
├── channels
│ ├── consumer.js
│ └── index.js
└── packs
| └── application.js
|ـــ post
|__ index.js
This simple way to use webpack same old rails style.
回答3:
I will try to answer your question with coffee script since its better for understanding, the key is using event 'turbolinks:load' to check the content if rails load post page with new method, the page basically by default will have class .posts.new
inside your app.post.coffee
class App.Post
render: ->
console.log "Post Edit / new render"
console.log "all this script below will only run"
console.log "if you open Post with new or edit method"
$(document).on "turbolinks:load", ->
if $(".posts.new")[0] || $(".posts.edit")[0]
post = new App.Post
post.render()
inside your app.author.coffee
class App.Author
render: ->
console.log "Author Edit / newrender"
console.log "all this script below will only run"
console.log "if you open Author with new or edit method"
$(document).on "turbolinks:load", ->
if $(".authors.new")[0] || $(".authors.edit")[0]
author = new App.Author
author.render()
and here is post.js if I convert my coffee script to js
App.Post = class Post {
render() {
console.log("Post Edit / new render");
console.log("all this script below will only run");
return console.log("if you open Post with new or edit method");
}
};
$(document).on("turbolinks:load", function() {
var post;
if ($(".posts.new")[0] || $(".posts.edit")[0]) {
post = new App.Post;
return post.render();
}
});
回答4:
This how I am serving page specific code with Turbolinks:
# javascript/packs/application.js
import {posts_show } from '../per_page/posts_show';
import {users_new } from '../per_page/users_new';
const pages = {posts_show, users_new};
document.addEventListener("turbolinks:load", () => {
# I am using gon to save the page name, but you can add page name to document body and then const page = document.body.className
const page = gon.controller_full
# check if method exist if true execute
if ('function' === typeof pages[page]){
new pages[page]
}
});
As you noticed, I'm compiling all the js inside one file, and execute them as they needed.
Here is an example of post_show:
# javascript/per_page/posts_show.js
import {default as share_menu_init} from './methods/share_menu_init.js'
export class posts_show {
constructor() {
share_menu_init()
... # some other code here
}
}
You could see that I'm importing the share_menu_init
module. If I need to share some methods between the pages, I store them inside modules and Webpack is smart enough to not create duplicates (if I import it twice, in posts_show and other pages) it will only organize files so that my posts_show
has access to this method.
const share_menu_init = () => {
... # some code here
}
export default share_menu_init
Not sure if its the best way to serve page-specific code, if you have one better, I would like to hear.
回答5:
You can use $.getScript
https://coderwall.com/p/xubjcq/handling-large-javascript-files-with-turbolinks
(function(){
var requiredScripts = [];
window.requireOnce = function (path, cb) {
if ($.inArray(path, requiredScripts) == -1) {
requiredScripts.push(path)
$.getScript(path, cb)
} else {
cb()
}
}
})();
Use it like this:
requireOnce('wysiwyg.js', function(){
$('textarea').wysiwyg()
});
来源:https://stackoverflow.com/questions/59493803/using-rails-6-where-do-you-put-your-page-specific-javascript-code