使用Rails 6,你应该将“页面特定”的JavaScript代码放在哪里?

28

我的问题与【使用Rails 3.1,你把"页面特定"的JavaScript代码放在哪里?】相同,只不过是针对Rails 6而非Rails 3.1。

假设我有一些想要用于我的帖子索引页面的JavaScript代码。我该把这个文件放在哪里,并且如何包含它?

在之前的问题中,答案通常利用了Rails Asset Pipeline。然而,对于Rails 6,我理解是它使用Webpacker代替Asset Pipeline来处理JavaScript文件。

注意:我不希望这个文件被始终包含。例如,如果我在作者索引页面上,我不想包含用于帖子索引页面的JavaScript文件。为什么呢?想象一下,如果在帖子索引页面的JS文件中有$('li').on('click', changeColor);,我可能不希望该代码在作者索引页面上运行,但如果该文件被包含,它将运行。您可以通过命名空间来避免此问题,但我认为不包含不必要的文件会更清晰(也更高效)。


2
你解决了吗?我也遇到了同样的问题,有些代码我只想在特定页面运行。 - truongnm
5个回答

21

我将按照使用 Webpack 和 Webpacker 的经验难度逐渐增加的顺序描述一些选项。

  1. 忘记页面特定的 JavaScript,将所有内容都放在 application.js 打包文件中。这绝对是最容易理解的方法。对于一个小应用程序来说,权衡可能是值得的,因为拥有一个更易于维护的应用程序可能会超过学习如何使用 Webpack 将您的 JS 代码最佳拆分的成本,并且性能提升也不大。

  2. 使用动态导入来延迟加载您的页面特定代码。在这种情况下,您仍将所有 JavaScript 放置在 application.js 依赖图中,但并非所有代码都会预加载。当编译您的 JS 时,Webpack 会识别动态的 import() 函数,并将导入的块拆分成单独的文件,可以使用 JS 框架路由器或简单的触发器在浏览器中按需加载。

    例如,您可以编写以下代码:

  3. if (document.querySelectorAll(".post-index").length) {
      import("./post/index") // webpack will load this JS async
    }
    
    使用页面特定的“packs”,结合 splitChunks 配置 API。在这种情况下,您不会使用一个 application.js pack,而是为每个要单独处理的页面构建一个包,例如 posts.jsadmin.js 等。使用 splitChunks 插件意味着您的捆绑包可以正确地共享代码。我强烈建议您慎重考虑这种方法,直到您了解 Webpack 的工作原理或者愿意经过学习过程选择这条路。Webpack 通常最适合假设您每个页面只使用一个入口点,否则,除非您知道自己在做什么,否则您可能会在捆绑包中复制代码。

我正在开发一个通过模板包含向ActiveRecord表单添加表单项的Gem。由于它仅在管理员表单页面上使用,因此我不希望在每个页面都包含我的Javascript。我考虑只需将Javascript置于模板中即可。你有什么建议? - albertski

7

让我们在一个空的Rails 6应用程序中浏览此目录的内容。

▶ tree app/javascript
app/javascript
├── channels
│   ├── consumer.js
│   └── index.js
└── packs
    └── application.js

2 directories, 3 files

对于我们来说,packs目录非常重要,让我们看看它包含了什么。

// app/javascript/application.js
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")

Webpack有一个入口点的概念,即当它开始编译JavaScript代码时首先要查找的文件。

Webpacker gem创建了一个应用程序包,位于app/javascript/packs下的application.js文件。如果您还记得assets pipeline,该文件相当于app/assets/javascripts/application.js文件。

简单示例

如何将JavaScript添加到Ruby on Rails(将JavaScript作为模块添加):

  1. 在Javascript路径中创建文件夹 app/javascript,例如:'post'

  2. 在文件夹中添加JavaScript文件,例如:index.js

  3. 在app/javascript/application.js中添加'post'模块 -> require('post')

    require("@rails/ujs").start() require("turbolinks").start() require("@rails/activestorage").start() require("channels") require("post")

让我们在添加'post'模块后浏览Rails 6应用程序中此目录的内容

▶ tree app/javascript
app/javascript
├── channels
│   ├── consumer.js
│   └── index.js
└── packs
|    └── application.js
|ـــ post
     |__ index.js

这是使用Webpack来模仿传统Rails风格的简单方法。

3
我在问题中忘记提到这一点(我将更新问题以包括它),但是采用那种方法似乎会始终包含app/javascript/post/index.js,而我只想在访问特定页面时才包含它。例如,当您访问作者索引页面时,我不希望其被包含,因为代码可能会产生冲突。 - Adam Zerner
此外,我的 app/javascript/packs/application.js 文件顶部包含以下注释:_"此文件是由Webpack自动编译的,以及该目录中存在的任何其他文件。鼓励您将实际应用程序逻辑放置在app/javascript中的相关结构中,并仅使用这些pack文件引用该代码,以便进行编译。"_ 我不清楚它想表达什么意思。那个 application.js 文件的内容会被覆盖吗?_"仅使用这些pack文件引用该代码,以便进行编译"_ 部分是什么意思? - Adam Zerner
我认为这可能意味着你应该使用application.js作为入口点来要求所有其他模块,随后它将渲染所有其他脚本。 - H.Sdq
@H.Sdq 是的,我添加了 application.js 来处理所有脚本。 - user11544535

0
你应该这样做,创建一个文件夹结构来匹配你的视图:
app/javascript
├── channels
│   ├── consumer.js
│   └── index.js
└── packs
     └── application.js
     └── post
         └── index.js

然后在你的视图index.js中,你想要包含:

<%= javascript_pack_tag('post/index') %>

添加完这个之后,在你的application.rb中添加:

config.assets.precompile += ['listings/index.js']

这样你只会在需要它的特定视图中加载它,你可以继续添加更多的资源,因为你正在制作视图特定的JS。

注意事项:

  • 一个常见的争论是,你可能会意外地推送缺少预编译列表中的资源的生产代码,所以你应该加载所有的JS,但这只是懒惰,如果缺少一个资源,你甚至无法加载页面,如果你推送缺少资源的代码,你什么都没有测试。

参考资料:

  1. https://guides.rubyonrails.org/asset_pipeline.html#how-to-use-the-asset-pipeline

0

这是我如何使用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]
    }
});

正如您所注意到的,我正在将所有js编译到一个文件中,并在需要时执行它们。

这里是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
    }
}

你可以看到我正在导入share_menu_init模块。如果我需要在页面之间共享一些方法,我会将它们存储在模块中,Webpack足够聪明,不会创建重复项(如果我在posts_show和其他页面中导入两次),它只会组织文件,以便我的posts_show可以访问此方法。
const share_menu_init = () => {
    ... # some code here
}
export default share_menu_init

不确定这是否是提供特定页面代码的最佳方式,如果您有更好的方法,我很乐意听取。


-1
你可以使用$.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()
    }
  }
})();

使用方法如下:

requireOnce('wysiwyg.js', function(){
  $('textarea').wysiwyg()
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接