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

Using Rails 6, where do you put your “page specific” JavaScript code?

我这里的问题和Using Rails 3.1, where do you put your "page specific" JavaScript code?一样,只是为了Rails 6 而不是Rails 3.1.

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

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

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

让我们在一个空的 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 文件的形式创建应用程序包。如果你还记得资产管道,这个文件相当于 app/assets/javascripts/application.js 文件

简单示例

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

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

  2. 将您的 javascript 文件添加到 index.js

    等文件夹中
  3. app/javascript/application.js中添加post模块 -> require('post')

    require("@rails/ujs").start() 要求(“turbolinks”)。开始() 要求(“@rails/activestorage”)。开始() 要求(“频道”) 要求(“post”)

让我们在添加post模块post后的Rails6应用程序中查看此目录的内容

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

这种简单的方式使用 webpack 相同的旧 rails 风格。

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

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

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

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

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

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

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

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

您应该这样做的方式是创建一个文件夹结构来匹配您的观点:

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