简体   繁体   English

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

[英]Rails 5/6: How to include JS functions with webpacker?

I am trying to update a Rails 3 app to Rails 6 and I have problems with the now default webpacker since my Javascript functions are not accessible.我正在尝试将 Rails 3 应用程序更新到 Rails 6,但由于我的 Javascript 函数无法访问,我在使用现在默认的 webpacker 时遇到了问题。

I get: ReferenceError: Can't find variable: functionName for all js function triggers.我得到: ReferenceError: Can't find variable: functionName for all js function triggers。

What I did is:我所做的是:

  • create an app_directory in /app/javascript在 /app/javascript 中创建一个 app_directory
  • copied my development javascript file into the app_directory and renamed it to index.js将我的开发 javascript 文件复制到 app_directory 并将其重命名为 index.js
  • added console.log('Hello World from Webpacker');添加了console.log('Hello World from Webpacker'); to index.js到 index.js
  • added import "app_directory";添加了import "app_directory"; to /app/javascript/packs/application.js到 /app/javascript/packs/application.js
  • added to /config/initializers/content_security_policy.rb:添加到/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

I get 'Hello World from Webpacker' logged to console, but when trying to access a simple JS function through <div id="x" onclick="functionX()"></div> in the browser I get the reference error.我将“来自 Webpacker 的 Hello World”记录到控制台,但是当我尝试通过浏览器中的<div id="x" onclick="functionX()"></div>访问一个简单的 JS 函数时,我收到了参考错误。

I understand that the asset pipeline has been substituted by webpacker, which should be great for including modules, but how should I include simple JS functions?我知道资产管道已被 webpacker 取代,这对于包含模块应该很好,但是我应该如何包含简单的 JS 函数? What am I missing?我错过了什么?

Thanks in advance?提前致谢?

For instructions on moving from the old asset pipeline to the new webpacker way of doing things, you can see here:有关从旧的资产管道迁移到新的 webpacker 工作方式的说明,您可以在此处查看:

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

This is a howto for moving from the asset pipeline to webpacker in Rails 5.2, and it gives you an idea of how things are different in Rails 6 now that webpacker is the default for javascript.这是在 Rails 5.2 中从资产管道迁移到 webpacker 的方法,它让您了解 Rails 6 中的情况有何不同,因为 webpacker 是 javascript 的默认设置。 In particular:特别是:

Now it's time to move all of your application JavaScript code from app/assets/javascripts/ to app/javascript/.现在是时候将所有应用程序 JavaScript 代码从 app/assets/javascripts/ 移动到 app/javascript/。

To include them in the JavaScript pack, make sure to require them in app/javascript/pack/application.js:要将它们包含在 JavaScript 包中,请确保在 app/javascript/pack/application.js 中要求它们:

 require('your_js_file')

So, create a file in app/javascript/hello.js like this:因此,在app/javascript/hello.js创建一个文件,如下所示:

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

Then, in app/javascript/packs/application.js , add this line:然后,在app/javascript/packs/application.js ,添加以下行:

require("hello")

(note that the extension isn't needed) (请注意,不需要扩展名)

Now, you can load up a page with the browser console open and see the "Hello!"现在,您可以在浏览器控制台打开的情况下加载页面并看到“Hello!” message in the console.控制台中的消息。 Just add whatever you need in the app/javascript directory, or better yet create subdirectories to keep your code organized.只需在app/javascript目录中添加您需要的任何内容,或者更好地创建子目录以保持您的代码井井有条。


More information:更多信息:

This question is cursed.这个问题被诅咒了。 The formerly accepted answer is not just wrong but grotesquely wrong, and the most upvoted answer is still missing the mark by a country mile.以前被接受的答案不仅是错误的,而且是极其错误的,而且最受好评的答案仍然是一英里之内。

anode84 above is still trying to do things the old way, and webpacker will get in your way if you try that.上面的阳极84仍在尝试以旧方式做事,如果您尝试这样做,webpacker会妨碍您。 You have to completely change the way you do javascript and think about javascript when you move to webpacker.当你转向 webpacker 时,你必须彻底改变你做 javascript 的方式并考虑 javascript。 There is no "scoping issue".不存在“范围问题”。 When you put code in a web pack it's self-contained and you use import/export to share code between files.当您将代码放入网络包时,它是独立的,您可以使用导入/导出在文件之间共享代码。 Nothing is global by default.默认情况下没有什么是全局的。

I get why this is frustrating.我明白为什么这令人沮丧。 You're probably like me, and accustomed to declaring a function in a javascript file and then calling it in your HTML file.您可能和我一样,习惯于在 javascript 文件中声明一个函数,然后在 HTML 文件中调用它。 Or just throwing some javascript at the end of your HTML file.或者只是在 HTML 文件的末尾添加一些 javascript。 I have been doing web programming since 1994 (not a typo), so I've seen everything evolve multiple times.我从 1994 年开始从事网络编程(不是打字错误),所以我看到一切都在多次演变。 Javascript has evolved. Javascript 已经发展。 You have to learn the new way of doing things.你必须学习新的做事方式。

If you want to add an action to a form or whatever, you can create a file in app/javascript that does what you want.如果您想向表单或其他内容添加操作,您可以在 app/javascript 中创建一个文件来执行您想要的操作。 To get data to it, you can use data attributes, hidden fields, etc. If the field doesn't exist, then the code doesn't run.要获取数据,您可以使用数据属性、隐藏字段等。如果该字段不存在,则代码不会运行。

Here's an example that you might find useful.这是一个您可能会觉得有用的示例。 I use this for showing a popup if a form has a Google reCAPTCHA and the user hasn't checked the box at the time of form submission:如果表单具有 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;
        }
      }
    });
  });
});

Note that this is self-contained.请注意,这是自包含的。 It does not rely on any other modules and nothing else relies on it.它不依赖于任何其他模块,也没有其他任何东西依赖它。 You simply require it in your pack(s) and it will watch all form submissions.您只需将它放在您的包中,它就会监视所有表单提交。

Here's one more example of loading a google map with a geojson overlay when the page is loaded:这是在加载页面时加载带有 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); 
  });
});

When the page loads, I look for divs with the class "shuttle-route-version-map".当页面加载时,我会查找类为“shuttle-route-version-map”的 div。 For each one that I find, the data attribute "shuttleRouteVersionId" (data-shuttle-route-version-id) contains the ID of the route.对于我找到的每一个,数据属性“shuttleRouteVersionId”(data-shuttle-route-version-id)都包含路由的 ID。 I have stored the geojson in a hidden field that can be easily queried given that ID, and I then initialize the map, add the geojson, and then set the map center and bounds based on that data.我将 geojson 存储在一个隐藏字段中,根据该 ID 可以轻松查询该字段,然后初始化地图,添加 geojson,然后根据该数据设置地图中心和边界。 Again, it's self-contained except for the Google Maps functionality.同样,除了谷歌地图功能外,它是独立的。

You can also learn how to use import/export to share code, and that's really powerful.您还可以学习如何使用导入/导出来共享代码,这真的很强大。

So, one more that shows how to use import/export.因此,还有一个展示了如何使用导入/导出。 Here's a simple piece of code that sets up a "watcher" to watch your location:这是一段简单的代码,它设置了一个“观察者”来观察你的位置:

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;
  }
}

That exports two functions: "watch_position" and "stop_watching_position".导出两个函数:“watch_position”和“stop_watching_position”。 To use it, you import those functions in another file.要使用它,您需要在另一个文件中导入这些函数。

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;
    });
  }
});

When the page loads, we look for fields called "driver_location_check_latitude" and "driver_location_check_longitude".当页面加载时,我们查找名为“driver_location_check_latitude”和“driver_location_check_longitude”的字段。 If they exist, we set up a watcher with a callback, and the callback fills in those fields with the latitude and longitude when they change.如果它们存在,我们会设置一个带有回调的观察者,当它们发生变化时,回调会用纬度和经度填充这些字段。 This is how to share code between modules.这就是如何在模块之间共享代码。

So, again, this is a very different way of doing things.所以,再一次,这是一种非常不同的做事方式。 Your code is cleaner and more predictable when modularized and organized properly.如果模块化和组织得当,您的代码会更清晰、更可预测。

This is the future, so fighting it (and setting "window.function_name" is fighting it) will get you nowhere.这是未来,因此与之抗争(并设置“window.function_name”就是与之抗争)将一事无成。

Looking at how webpacker "packs" js files and functions:查看 webpacker 如何“打包”js 文件和函数:

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

  function myFunction() {...}

So webpacker stores these functions within another function, making them inaccessible.所以 webpacker 将这些函数存储在另一个函数中,使它们无法访问。 Not sure why that is, or how to work around it properly.不知道为什么会这样,或者如何正确解决它。

There IS a workaround, though.不过,有一种解决方法。 You can:你可以:

1) change the function signatures from: 1) 将函数签名从:

function myFunction() { ... }

to:到:

window.myFunction = function() { ... }

2) keep the function signatures as is, but you would still need to add a reference to them as shown here : window.myFunction = myFunction 2)保持功能的签名,是的,但你仍然需要添加到他们的参考如图所示这里window.myFunction = myFunction

This will make your functions globally accessible from the "window" object.这将使您的功能可以从“窗口”对象全局访问。

Replace the code in your custom java Script file from您的自定义的Java脚本文件替换代码

function function_name() {// body //}

to

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

From the official rails app guide:来自官方 Rails 应用指南:

Where to Stick Your JavaScript在哪里粘贴你的 JavaScript

Whether you use the Rails asset pipeline or add a tag directly to a view, you have to make a choice about where to put any local JavaScript file.无论您是使用 Rails 资产管道还是直接向视图添加标签,您都必须选择放置任何本地 JavaScript 文件的位置。

We have a choice of three locations for a local JavaScript file:我们可以为本地 JavaScript 文件选择三个位置:

The app/assets/javascripts folder,the lib/assets/javascripts folder and the vendor/assets/javascripts folder app/assets/javascripts 文件夹、lib/assets/javascripts 文件夹和 vendor/assets/javascripts 文件夹

Here are guidelines for selecting a location for your scripts:以下是为脚本选择位置的指南:

Use app/assets/javascripts for JavaScript you create for your application.将 app/assets/javascripts 用于您为应用程序创建的 JavaScript。

Use lib/assets/javascripts for scripts that are shared by many applications (but use a gem if you can).将 lib/assets/javascripts 用于许多应用程序共享的脚本(但如果可以,请使用 gem)。

Use vendor/assets/javascripts for copies of jQuery plugins, etc., from other developers.使用 vendor/assets/javascripts 复制来自其他开发人员的 jQuery 插件等。 In the simplest case, when all your JavaScript files are in the app/assets/javascripts folder, there's nothing more you need to do.在最简单的情况下,当您的所有 JavaScript 文件都在 app/assets/javascripts 文件夹中时,您无需再执行任何操作。

Add JavaScript files anywhere else and you will need to understand how to modify a manifest file.在其他任何地方添加 JavaScript 文件,您将需要了解如何修改清单文件。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM