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 应用程序中处理图像?