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

Posted

技术标签:

【中文标题】使用 Rails 6,您将“特定于页面”的 JavaScript 代码放在哪里?【英文标题】:Using Rails 6, where do you put your “page specific” JavaScript code? 【发布时间】:2020-04-17 00:20:03 【问题描述】:

我的问题与Using Rails 3.1, where do you put your "page specific" javascript code? 相同,只是针对 Rails 6 而不是 Rails 3.1。

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

在上一个问题中,答案通常使用 Rails 资产管道。但是,对于 Rails 6,我的理解是它使用 webpacker 而不是 JavaScript 文件的资产管道。

注意:我不想总是包含该文件。例如。如果我在作者索引页面上,我不希望包含帖子索引页面的 JavaScript 文件。为什么?想象一下,我在帖子索引页面的 JS 文件中有 $('li').on('click', changeColor);。我可能不希望该代码在作者索引页面上运行,但如果包含该文件,它将运行。您可以通过命名空间来解决这个问题,但我认为不包含不必要的文件会更简洁(并且性能更高)。

【问题讨论】:

你解决了吗?我遇到了同样的问题,有些代码我只想在特定页面上运行 【参考方案1】:

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

    忘记页面特定的 JavaScript 并将所有内容放在 application.js 包中。这绝对是最容易理解的方法。对于小型应用程序,这种权衡可能是值得的,因为拥有一个更易于维护的应用程序可能会超过学习如何最好地使用 Webpack 拆分 JS 代码而几乎没有性能提升的额外成本。

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

    例如,您的代码可能如下所示:

    if (document.querySelectorAll(".post-index").length) 
      import("./post/index") // webpack will load this JS async
    
    
    结合使用页面特定的“包”和the splitChunks configuration API。在这种情况下,不是使用 application.js 包,而是为每个要区别对待的页面构建一个包,例如,posts.jsadmin.js 等。使用 splitChunks 插件意味着您的包可以正确共享代码。我强烈建议您谨慎使用这种方法,直到您了解 Webpack 的工作原理或愿意在选择此路径时经历学习 Webpack 的过程。 Webpack 通常在您每页仅使用一个入口点的假设下效果最佳,否则,除非您知道自己在做什么,否则您最终可能会跨包重复代码。

【讨论】:

我正在开发一个 Gem,它通过模板包含将表单项添加到 ActiveRecord 表单中。我讨厌必须在每个页面上都包含我的 Javascript,因为它只会在管理表单页面上。我正在考虑将 Javascript 嵌入模板。你会推荐什么?【参考方案2】:

让我们在一个空的 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 管道,这个文件相当于 app/assets/javascripts/application.js 文件

简单示例

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

    在 Javascript 路径 app/javascript ex: 'post' 中创建文件夹

    将您的 javascript 文件添加到 index.js 等文件夹中

    app/javascript/application.js 中添加postmodule -> require('post')

    require("@rails/ujs").start() 需要(“turbolinks”).start() 需要("@rails/activestorage").start() 要求(“渠道”) 要求(“发布”)

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

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

这种简单的方式使用 webpack 与旧的 rails 样式相同。

【讨论】:

我忘了在问题中提到这一点(我会更新问题以包含它),但是通过这种方法,app/javascript/post/index.js 似乎会一直被包含在内,而我只想要当您在特定页面上时将其包含在内。例如,我不希望在您访问作者索引页面时包含它,因为代码可能有冲突。 另外,我的app/javascript/packs/application.js 文件的顶部包含以下注释:“此文件与此目录中存在的任何其他文件一起由 Webpack 自动编译。您是鼓励将您的实际应用程序逻辑放在 app/javascript 中的相关结构中,并且仅使用这些包文件来引用该代码,以便对其进行编译。” 我不清楚它想说什么。 application.js 文件的内容会被覆盖吗? "and only use these pack files to reference that code so it will be compiled"部分是什么意思? 我相信这可能意味着您应该使用 application.js 作为入口点来要求所有其他模块,随后它也会呈现所有其他脚本。 @H.Sdq 是的,我添加了application.js 来处理所有脚本【参考方案3】:

这就是我使用 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

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

【讨论】:

【参考方案4】:

您应该这样做的方式是创建一个文件夹结构以匹配您的视图:

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 但这只是懒惰,如果页面缺少资产,您甚至无法加载页面,如果你推送的代码缺少你没有测试任何东西的资产。

参考资料:

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

【讨论】:

【参考方案5】:

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

【讨论】:

以上是关于使用 Rails 6,您将“特定于页面”的 JavaScript 代码放在哪里?的主要内容,如果未能解决你的问题,请参考以下文章

Webpack 中特定于页面的 JavaScript 以及第三方脚本和应用程序范围的脚本

通过回发但特定于页面用户的变量的持久性

您将 Rails 应用程序放在服务器的啥位置?您与啥用户一起部署?

将javascript添加到页面或外部javascript文件[重复]

Rails 5.2 和 webpacker 3.4.3:部署到 Heroku 时资产未编译

您将如何在 django 中设置“上下文”类型的对象