はじめに
- Rails7で標準装備となっているimportmapを使用しGoogleMapsを表示させます。スクリプトを単体で読み込ませる方法と全体で読み込ませる方法について解説します。
- importmap以外にもJavaScriptについても少々解説するので、"JavaScriptはこれから"という人にも分かりやすい内容になっているかもしれません。
実行環境
Ruby 3.2.2
Rails 7.0.4.3
やりたいこと
マーカーが配置された Google マップをウェブサイトに追加する
TL;DR(お急ぎの方)
まずはやりたいことを実現可能なコードを掲載し、後のパートで解説いたします。
GoogleMapsのスクリプトを読み込むには大きく以下2つのアプローチがあります。
- ページ全体で読み込む
- 特定ページのみで読み込む
ページ全体で読み込む
GoogleMapsをページ全体で読み込ませることは少ないと思いますが、他のスクリプトも同じような記述でページ全体で同様のスクリプトを読み込ませることができます。
注意:読み込みはページ全体で行っていますが、実行はGoogleMapsAPIを叩いている特定ページのみです。
1. jsファイルをjavascriptフォルダに配置
export function initMap() {
const tower = { lat: 35.6586, lng: 139.7454 }; // 東京タワーを指定
const map = new google.maps.Map(document.getElementById("map"), {
zoom: 18,
center: tower,
});
const marker = new google.maps.Marker({
position: tower,
map: map,
});
}
window.initMap = initMap
2. importmapにピン留め
pin "application", preload: true
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"
pin "map" # 追加
3. Mapを埋め込むHTMLとinitMapを実行するスクリプトを記載
<h3>My Google Maps Demo</h3>
<div id="map" style="height: 100vh; width: 100%;"></div>
<script
src="https://maps.googleapis.com/maps/api/js?key=ENV['GOOGLE_MAP_API_KEY']&callback=initMap&v=weekly"
defer
></script>
4. エントリーポイントで関数読み込み
import "@hotwired/turbo-rails";
import "controllers";
import "map"; // 追加
特定ページのみで読み込む
GoogleMapsは一般的には特定のページのみで表示させたいニーズが多いかと思います。
1. jsファイルをjavascriptフォルダに配置
function initMap() {
const tower = { lat: 35.6586, lng: 139.7454 }; // 東京タワーを指定
const map = new google.maps.Map(document.getElementById("map"), {
zoom: 18,
center: tower,
});
const marker = new google.maps.Marker({
position: tower,
map: map,
});
}
window.initMap = initMap;
2. importmapにピン留め
pin "application", preload: true
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"
pin "map" # 追加
3. Mapを埋め込むHTMLとmap.jsを個別で読み込むためのヘルパーメソッドを記載
<% content_for :js do %>
<%= javascript_import_module_tag "map" %>
<% end %>
<h3>My Google Maps Demo</h3>
<div id="map" style="height: 100vh; width: 100%;"></div>
<script src="https://maps.googleapis.com/maps/api/js?key=ENV['GOOGLE_MAP_API_KEY']&callback=initMap&v=weekly"
defer></script>
4. content_forをyeild
<!DOCTYPE html>
<html>
<head>
略
<%= javascript_importmap_tags %>
<%= yield(:js) %> #追加
</head>
略
</html>
解説
importmapについて軽く紹介し、コードを解説します。
Import Mapsとimportmap
Import Mapsは、そもそもRailsでなくJavaScript ES6で提供している機能で、モジュールのエイリアスとパスを紐づけ(マッピング)ます。これまではモジュールをインポートするためにはimport "./map.js";
のようにインポート元のパスを書く必要がありましたが、Import Mapsを利用することによってimport "map";
のように記述することができます。ES6単体ではheadタグ内に以下のscriptを記述することでモジュールをマッピングすることができます。Rails7で導入されたimportmapではこれらのタグを生成するヘルパーメソッドなどを提供しています。
<head>
<script type="importmap" data-turbo-track="reload">{
"imports": {
略
"map": "/assets/map/map-549e7b70a8aee6f70d68a636f7efc4d5cf27b058b36c4a32de8655c7a6a1e054.js",
略
}
}</script>
略
<script type="module">import "application"</script>
<script type="module">import "map"</script>
</head>
ページ全体で読み込む(解説)
importmapが何者かざっくりわかったところで、前述のコードについて解説していきます。
1. jsファイルをjavascriptフォルダに配置
export function initMap() {
略
}
window.initMap = initMap;
export
と記述することで外部からもinitMap
関数が呼べるようにしています。また、initMap
関数はインポートしただけではコールバック関数として呼び出すことができないため、一番大きなスコープであるグローバル関数として登録します。
2. importmapにピン留め
pin "application", preload: true
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"
pin "map" # 追加
pin "map", to: "map/map.js" # 相対パスで指定する場合
ここではマッピングする内容を一括して記載します。pin “map”
を追記することでjavascriptディレクトリに配置したmap.jsを”map”
というエイリアスでインポートできるようになります。app/javascript/map/map.js
に配置する場合など、他フォルダに存在する場合は相対パスで記載します。
<!DOCTYPE html>
<html>
<head>
略
<%= javascript_importmap_tags %>
</head>
略
</html>
# 生成されたHTML
<head>
<script type="importmap" data-turbo-track="reload">{
"imports": {
略
"map": "/assets/map/map-549e7b70a8aee6f70d68a636f7efc4d5cf27b058b36c4a32de8655c7a6a1e054.js",
略
}
}</script>
略
<script type="module">import "application"</script>
</head>
javascript_importmap_tags
ヘルパーにて生成されたHTMLを確認すると、先ほどマッピングした内容でインポートするscriptタグを生成しています。importmap属性のscript内では、JSON形式で”map”
というエイリアスでアセット化されたmapスクリプトを参照していることがわかります。また、スクリプト実行にあたり、module属性のscript内でapplication.jsがエントリーポイントとしてインポートされます。
3. Mapを埋め込むHTMLとinitMapを実行するスクリプトを記載
<h3>My Google Maps Demo</h3>
<div id="map" style="height: 100vh; width: 100%;"></div>
<script
src="https://maps.googleapis.com/maps/api/js?key=ENV['GOOGLE_MAP_API_KEY']&callback=initMap&v=weekly"
defer
></script>
scriptタグ内のsrc属性ではAPIキーを環境変数dotenv経由で取得するようにしています。
また、defer属性をつけることで、HTMLや他のスクリプトの読み込みが完了した後にGoogleMapsAPIを叩きます。このdefer属性がないと、コールバック関数であるinitMap
の実行に失敗してしまいます。
4. エントリーポイントで関数読み込み
import "@hotwired/turbo-rails";
import "controllers";
import "map"; # 追加
map
スクリプトをインポートします。application.jsでインポートするのは、同ファイルがあらゆるスクリプトを実行するエントリーポイント(起点)となっているからです。
特定ページのみ読み込む(解説)
1. jsファイルをjavascriptフォルダに配置
function initMap() {
略
}
window.initMap = initMap;
今回はapplication.js
など他のスクリプトから参照することはないため、export
は不要です。
2. importmapにピン留め
pin "application", preload: true
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"
pin "map" # 追加
pin "map", to: "map/map.js" # 相対パスで指定する場合
ページ全体で読み込む場合と同様にマッピング内容を設定します。
3. Mapを埋め込むHTMLとmap.jsを個別で読み込むためのヘルパーメソッドを記載
<% content_for :js do %>
<%= javascript_import_module_tag "map" %>
<% end %>
<h3>My Google Maps Demo</h3>
<div id="map" style="height: 100vh; width: 100%;"></div>
<script src="https://maps.googleapis.com/maps/api/js?key=ENV['GOOGLE_MAP_API_KEY']&callback=initMap&v=weekly"
defer></script>
# 生成されたHTML
<head>
<script type="importmap" data-turbo-track="reload">{
"imports": {
略
"map": "/assets/map/map-549e7b70a8aee6f70d68a636f7efc4d5cf27b058b36c4a32de8655c7a6a1e054.js",
略
}
}</script>
略
<script type="module">import "application"</script>
<script type="module">import "map"</script>
</head>
ページ全体で読み込む場合と異なる箇所としてcontent_for
メソッドを追加しています。引数に任意のシンボルを取り(ここでは:js
)、ブロックにjavascript_import_module_tag
ヘルパー引数”map”
を渡すことで、map.jsを読み込むHTMLを作成しています。(正確には後述のyield
が必要)
4. content_forをyeild
<!DOCTYPE html>
<html>
<head>
略
<%= javascript_importmap_tags %>
<%= yield(:js) %> #追加
</head>
略
</html>
content_for
のコンテンツを呼び出すためyield
しています。2つの組み合わせにより、該当ページでのみ<script type="module">import "map"</script>
が生成され、スクリプトを呼ぶことができます。
さいごに
importmapには他にもCDNを読み込む機能なども提供しています。
以下が参考になりましたので合わせて読むことをおすすめします。
Rails 7: importmap-rails gem README(翻訳)
Rails 7.0 で標準になった importmap-rails とは何なのか?
Rails 7 でファイルごとに JavaScript を分けて使えるようにする