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

SEJ*_*EJU 4 javascript ruby-on-rails webpack webpacker ruby-on-rails-6

我正在尝试将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
    
    Run Code Online (Sandbox Code Playgroud)

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

我了解到资产管道已被webpacker取代,这对于包含模块来说应该是很好的选择,但是我应该如何包括简单的JS函数?我想念什么?

提前致谢?

Mic*_*ney 20

有关从旧的资产管道迁移到新的 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 包中,请确保在 app/javascript/pack/application.js 中要求它们:

require('your_js_file')
Run Code Online (Sandbox Code Playgroud)

所以,app/javascript/hello.js像这样创建一个文件:

console.log("Hello from hello.js");
Run Code Online (Sandbox Code Playgroud)

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

require("hello")
Run Code Online (Sandbox Code Playgroud)

(请注意,不需要扩展名)

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


更多信息:

这个问题被诅咒了。以前被接受的答案不仅是错误的,而且是极其错误的,而且最受好评的答案仍然是一英里之内。

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

我明白为什么这令人沮丧。您可能和我一样,习惯于在 javascript 文件中声明一个函数,然后在 HTML 文件中调用它。或者只是在 HTML 文件的末尾添加一些 javascript。我从 1994 年开始从事网络编程(不是打字错误),所以我看到一切都在多次演变。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;
        }
      }
    });
  });
});
Run Code Online (Sandbox Code Playgroud)

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

这是在加载页面时加载带有 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); 
  });
});
Run Code Online (Sandbox Code Playgroud)

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

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

因此,还有一个展示了如何使用导入/导出。这是一段简单的代码,它设置了一个“观察者”来观察你的位置:

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;
  }
}
Run Code Online (Sandbox Code Playgroud)

导出两个函数:“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;
    });
  }
});
Run Code Online (Sandbox Code Playgroud)

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

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

这是未来,因此与之抗争(并设置“window.function_name”就是与之抗争)将一事无成。


小智 6

查看webpacker如何“打包” js文件和功能:

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

  function myFunction() {...}
Run Code Online (Sandbox Code Playgroud)

因此,webpacker将这些功能存储在另一个功能中,从而使其无法访问。不知道为什么会这样,或者不确定如何正确解决。

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

1)从以下位置更改功能签名:

function myFunction() { ... }
Run Code Online (Sandbox Code Playgroud)

至:

window.myFunction = function() { ... }
Run Code Online (Sandbox Code Playgroud)

2)保持功能的签名,是的,但你仍然需要添加到他们的参考如图所示在这里window.myFunction = myFunction

这将使您的功能可从“窗口”对象全局访问。

  • 这是故意的,因为 webpacker 鼓励您使用新的模块功能,这样您就不会出现名称冲突等问题。Chaney 的回复解释了如何正确执行此操作。 (3认同)
  • 请不要使用此解决方案,因为您不想阅读 Chaney 的答案,如果您想以正确的方式进行操作,请花几分钟了解 Chaney 的答案。 (2认同)