Rails 5/6:如何在 webpacker 中包含 JS 函数?

Posted

技术标签:

【中文标题】Rails 5/6:如何在 webpacker 中包含 JS 函数?【英文标题】:Rails 5/6: How to include JS functions with webpacker? 【发布时间】:2019-06-27 07:32:17 【问题描述】:

我正在尝试将 Rails 3 应用程序更新到 Rails 6,但由于无法访问我的 javascript 函数,因此现在默认的 webpacker 存在问题。

我得到:ReferenceError: Can't find variable: functionName 用于所有 js 函数触发器。

我所做的是:

在 /app/javascript 中创建一个 app_directory 将我的开发 javascript 文件复制到 app_directory 并将其重命名为 index.js 将console.log('Hello World from Webpacker'); 添加到 index.js 将import "app_directory"; 添加到/app/javascript/packs/application.js

添加到 /config/initializers/content_security_policy.rb:

Rails.application.config.content_security_policy do |policy|
  policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development?
end

我将“来自 Webpacker 的 Hello World”记录到控制台,但是当尝试在浏览器中通过 <div id="x" onclick="functionX()"></div> 访问一个简单的 JS 函数时,我得到了参考错误。

我知道资产管道已经被webpacker取代了,这对于包含模块应该很好,但是我应该如何包含简单的JS函数?我错过了什么?

提前致谢?

【问题讨论】:

当您说要添加一个名为 index.js 的文件时,您要将它添加到应用程序的哪个部分?什么目录? @Mark 我正在使用 Rails 6 pre,截至今天,它在 app/assets 中没有默认的 JS 目录,并且在 application.html.erb 中没有 JavaScript include 标签。发现我必须重新创建资产管道位置,但我仍然怀疑当 R6 准备好时它将如何工作.... 这篇文章 blog.capsens.eu/… 解释了为什么这个问题是基于错误的假设,如何使用 webpacker 以及为什么像 sprockets 一样使用它不起作用 【参考方案1】:

来自官方 Rails 应用指南:

在哪里粘贴你的 JavaScript

您是否使用 Rails 资产 管道或直接将标签添加到视图中,您必须制作一个 选择放置任何本地 JavaScript 文件的位置。

我们可以为本地 JavaScript 文件选择三个位置:

app/assets/javascripts 文件夹、lib/assets/javascripts 文件夹和 vendor/assets/javascripts 文件夹

以下是选择指南 脚本的位置:

为您创建的 JavaScript 使用 app/assets/javascripts 应用。

对许多人共享的脚本使用 lib/assets/javascripts 应用程序(但如果可以,请使用 gem)。

使用 vendor/assets/javascripts 获取 jQuery 插件等的副本,来自 其他开发商。在最简单的情况下,当你所有的 JavaScript 文件 在 app/assets/javascripts 文件夹中,没有其他内容了 需要做的。

在其他任何地方添加 JavaScript 文件,您需要了解如何 修改清单文件。

更多阅读: http://railsapps.github.io/rails-javascript-include-external.html

【讨论】:

非常感谢您的回答。我正在使用 Rails 6.0.0 pre 并且由于默认情况下资产中没有 JavaScript 目录,我认为他们将默认位置移动到应用程序目录中的 JavaScript,但我猜该目录实际上是用于 JavaScript 框架驱动的站点或仅节点,npm 模块。我在 assets + application.js 中重新创建了 JS 目录,现在它正在运行。我仍然很好奇在 Rails 6 中是否可以将标准 JS 文件打包到新的 JS 目录中......我能够登录到控制台,但不能调用函数...... 是的,6.0 中的新格式似乎不再将 javascript 保存在 assets 文件夹中。 当您发布此类内容时,请务必仔细查看。从链接页面的顶部:“Last updated 2012 年 12 月 31 日”最后一次更新是在 Rails 3 天。不确定原始调查员是否可以收回绿色检查,但这不是 Rails 6 的正确答案。 我提供的文字来自官方的rails指南,适用于最新版本的rails。该链接提供了进一步的解释。 Rails 6 NOT 将资产管道用于 javascript。该问题专门询问 Rails 6。因此,这是 NOT 正确的。时期。不幸的是,Rails 6 的指南有些过时了,即使是边缘指南。【参考方案2】:

看看webpacker如何“打包”js文件和函数:

/***/ "./app/javascript/dashboard/project.js":
/*! no static exports found */
/***/ (function(module, exports) 

  function myFunction() ...

所以 webpacker 将这些函数存储在另一个函数中,使它们无法访问。不知道为什么会这样,或者如何正确解决它。

不过,有一种解决方法。你可以:

1) 更改函数签名:

function myFunction()  ... 

到:

window.myFunction = function()  ... 

2) 保持函数签名不变,但您仍需要添加对它们的引用,如 here 所示: window.myFunction = myFunction

这将使您的函数可以从“窗口”对象全局访问。

【讨论】:

这是故意的,因为 webpacker 鼓励您使用新的模块功能,这样您就不会遇到名称冲突等问题。Chaney 的回复解释了如何正确执行此操作。 请不要使用此解决方案,因为您不想阅读 Chaney 的答案,如果您想以正确的方式进行操作,请花几分钟时间了解 Chaney 的答案。【参考方案3】:

有关从旧资产管道迁移到新的 webpacker 处理方式的说明,您可以在此处查看:

https://www.calleerlandsson.com/replacing-sprockets-with-webpacker-for-javascript-in-rails-5-2/

这是在 Rails 5.2 中从资产管道迁移到 webpacker 的操作指南,它让您了解 Rails 6 中的不同之处,因为 webpacker 是 javascript 的默认设置。特别是:

现在是时候将所有应用程序 JavaScript 代码从 app/assets/javascripts/ 到 app/javascript/。

要将它们包含在 JavaScript 包中,请确保在 应用程序/javascript/pack/application.js:

require('your_js_file')

所以,在app/javascript/hello.js 中创建一个文件,如下所示:

console.log("Hello from hello.js");

然后,在app/javascript/packs/application.js 中添加这一行:

require("hello")

(注意不需要扩展)

现在,您可以在浏览器控制台打开的情况下加载一个页面并看到“Hello!”控制台中的消息。只需在 app/javascript 目录中添加您需要的任何内容,或者最好创建子目录以保持您的代码井井有条。


更多信息:

这个问题被诅咒了。以前接受的答案不仅是错误的,而且是非常错误的,而最受支持的答案仍然是一个国家英里的目标。

上面的 anode84 仍在尝试以旧方式做事,如果您尝试这样做,webpacker 会妨碍您。当你迁移到 webpacker 时,你必须彻底改变你做 javascript 的方式并考虑 javascript。没有“范围问题”。当您将代码放入 web pack 时,它是独立的,您可以使用导入/导出在文件之间共享代码。默认情况下,没有什么是全局的。

我明白为什么这令人沮丧。你可能和我一样,习惯于在 javascript 文件中声明一个函数,然后在你的 HTML 文件中调用它。或者只是在 HTML 文件的末尾添加一些 javascript。自 1994 年以来,我一直在进行 Web 编程(不是拼写错误),所以我看到一切都发展了多次。 Javascript已经发展。你必须学习新的做事方式。

如果您想向表单或其他任何内容添加操作,您可以在 app/javascript 中创建一个文件来执行您想要的操作。要获取数据,可以使用数据属性、隐藏字段等。如果该字段不存在,则代码不会运行。

这是一个您可能会发现有用的示例。如果表单有 Google reCAPTCHA 并且用户在提交表单时没有选中该框,我会使用它来显示弹出窗口:

// For any form, on submit find out if there's a recaptcha
// field on the form, and if so, make sure the recaptcha
// was completed before submission.
document.addEventListener("turbolinks:load", function() 
  document.querySelectorAll('form').forEach(function(form) 
    form.addEventListener('submit', function(event) 
      const response_field = document.getElementById('g-recaptcha-response');
      // This ensures that the response field is part of the form
      if (response_field && form.compareDocumentPosition(response_field) & 16) 
        if (response_field.value == '') 
          alert("Please verify that you are not a robot.");
          event.preventDefault();
          event.stopPropagation();
          return false;
        
      
    );
  );
);

请注意,这是独立的。它不依赖于任何其他模块,也没有其他依赖于它。您只需在包中需要它,它就会监视所有表单提交。

这是在页面加载时加载带有 geojson 覆盖的谷歌地图的另一个示例:

document.addEventListener("turbolinks:load", function() 
  document.querySelectorAll('.shuttle-route-version-map').forEach(function(map_div) 
    let shuttle_route_version_id = map_div.dataset.shuttleRouteVersionId;
    let geojson_field = document.querySelector(`input[type=hidden][name="geojson[$shuttle_route_version_id]"]`);

    var map = null;

    let center = lat: 36.1638726, lng: -86.7742864;
    map = new google.maps.Map(map_div, 
      zoom: 15.18,
      center: center
    );

    map.data.addGeoJson(JSON.parse(geojson_field.value));

    var bounds = new google.maps.LatLngBounds();
    map.data.forEach(function(data_feature) 
      let geom = data_feature.getGeometry();
      geom.forEachLatLng(function(latlng) 
        bounds.extend(latlng);
      );
    );
    map.setCenter(bounds.getCenter());
    map.fitBounds(bounds); 
  );
);

当页面加载时,我会寻找具有“shuttle-route-version-map”类的 div。对于我找到的每一个,数据属性“shuttleRouteVersionId”(data-shuttle-route-version-id)都包含路线的 ID。我已将 geojson 存储在一个隐藏字段中,可以根据该 ID 轻松查询该字段,然后我初始化地图,添加 geojson,然后根据该数据设置地图中心和边界。同样,除了 Google 地图功能外,它是独立的。

您还可以学习如何使用导入/导出来共享代码,这真的很强大。

所以,还有一个展示如何使用导入/导出。下面是一段简单的代码,用于设置“观察者”来观察您的位置:

var driver_position_watch_id = null;

export const watch_position = function(logging_callback) 
  var last_timestamp = null;

  function success(pos) 
    if (pos.timestamp != last_timestamp) 
      logging_callback(pos);
    
    last_timestamp = pos.timestamp;
  

  function error(err) 
    console.log('Error: ' + err.code + ': ' + err.message);
    if (err.code == 3) 
      // timeout, let's try again in a second
      setTimeout(start_watching, 1000);
    
  

  let options = 
    enableHighAccuracy: true,
    timeout: 15000, 
    maximumAge: 14500
  ;

  function start_watching() 
    if (driver_position_watch_id) stop_watching_position();
    driver_position_watch_id = navigator.geolocation.watchPosition(success, error, options);
    console.log("Start watching location updates: " + driver_position_watch_id);  
  

  start_watching();


export const stop_watching_position = function() 
  if (driver_position_watch_id) 
    console.log("Stopped watching location updates: " + driver_position_watch_id);
    navigator.geolocation.clearWatch(driver_position_watch_id);
    driver_position_watch_id = null;
  

导出两个函数:“watch_position”和“stop_watching_position”。要使用它,您需要在另一个文件中导入这些函数。

import  watch_position, stop_watching_position  from 'watch_location';

document.addEventListener("turbolinks:load", function() 
  let lat_input = document.getElementById('driver_location_check_latitude');
  let long_input = document.getElementById('driver_location_check_longitude');

  if (lat_input && long_input) 
    watch_position(function(pos) 
      lat_input.value = pos.coords.latitude;
      long_input.value = pos.coords.longitude;
    );
  
);

页面加载时,我们会查找名为“driver_location_check_latitude”和“driver_location_check_longitude”的字段。如果它们存在,我们设置一个带有回调的观察者,当它们发生变化时,回调会用纬度和经度填充这些字段。这就是如何在模块之间共享代码。

所以,这又是一种非常不同的做事方式。如果模块化和组织得当,您的代码会更清晰、更可预测。

这是未来,所以与它抗争(设置“window.function_name”就是在抗争)会让你一事无成。

【讨论】:

这在 Rails 6 中不起作用。仍然遇到@anode84 指出的范围问题。 谢谢@MichaelChaney!我很感激 为所有难以理解的人(包括我自己)提炼这个答案的核心:不要在你的 html 中引用一些 JS 函数,而是在你的 JS 中添加一个 turbolinks 加载回调(这基本上是说,嘿,页面已加载并准备就绪)并在该回调中,查询您的元素(querySelector、getElementById 等)并为那里的点击添加一个侦听器。 @MichaelChaney 这个答案是救命稻草!随着 Javascript 的发展,我对 Javascript 的理解很糟糕,而且我承认我一直很懒,因为我不喜欢它作为一种语言。当我过渡到 Webpacker 时,我不明白为什么有这么多东西在破坏,为什么这么多安装教程都在将模块分配给 window。绝对是游戏规则的改变者。将花更多时间学习以确保我已经掌握了它。 很好的答案,我不得不潜入数小时的随机垃圾中才能找到这颗宝石。感谢您的帮助。【参考方案4】:

替换自定义 java 脚本文件中的代码 来自

function function_name() // body //

window.function_name = function() // body //

【讨论】:

人们为什么要这样做? @SebastianPalma 他们不应该。看我的正确答案。我不知道为什么有人进来并重复了已经在这里的错误答案。 你能用简单的措辞解释一下你的方法吗? @MichaelChaney 我认为官方文档没有根据 Rails 6 中使用的 webpacker 进行更新。 @HassamSaeed 你的答案是错误的。请阅读我上面的全部答案。您可能需要反复阅读才能理解它(我当然做到了)。我将回答您在上面链接的问题。

以上是关于Rails 5/6:如何在 webpacker 中包含 JS 函数?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 webpacker rails 中使用 ProvidePlugin?

如何在rails中使用webpacker安装tailwindcss [关闭]

如何在 Rails 6 和 webpacker 中使用 morris.js?

如何在 Rails / Webpacker / React 应用程序中处理图像?

如何在 Webpacker 中使用 Rails Url 助手/Rails 5.1 中的 React-rails

如何在 Elastic Beanstalk 容器中提供 Rails 应用程序的 webpack 资产?