毎度悩むBookmarkletのjsファイルのホスト先をFirebaseにしてみたら、何かいいことあるかなと試したもの
#使ったもの
毎度のことならがWindows環境です
- Windows10
- Cmder
- Node.js (v10.15.3)
- npm (6.4.1)
- javascript
#Firebaseを使えるようにするところまで
Firebaseは知っていたが使ったことは無かったので、初めてのFirebaseです。
##やったこと
###node.jsのインストール
何も考えずデフォルトのC:\Program Files\nodejs
にインストール
###環境変数の編集
コンピュータ>システムの詳細設定>環境変数で、ユーザの環境変数のPATHにC:\Program Files\nodejs
を追加
コマンドプロンプトでパスが通ったか確認
nodejsをインストールしたディレクトリ以外の場所で叩ければOK。
大丈夫ですね
##Firebaseのツールをインストール
npmのパスが取ってれば、どこで実行してもOKです。実行場所とインストール場所は無関係です。
npm install -g firebase-tools
> @google-cloud/functions-emulator@1.0.0-beta.5 postinstall C:\Users\xxx\AppData\Roaming\npm\node_modules\firebase-tools\node_modules\@google-cloud\functions-emulator
(省略)
+ firebase-tools@6.5.0
added 556 packages from 274 contributors in 114.727s
Windowsのユーザーディレクトリの中にいろいろ保存されます。
ここまでで準備完了。
##Firebaseのプロジェクト作成
ブラウザで、Firebase Console へ行き、プロジェクトの追加をする
プロジェクト名とかは適当につける。アナリティクスに地域を日本に、ロケーションをasia-northeast1(東京)に変更しておきました。なんとなく
##Firebaseにアップロードする作業場所を準備
まず、ローカルフォルダを作る。
Windows上に、C:\firebase_sample
フォルダを作成。空っぽのまま
次に、コマンドプロンプトで、C:\firebase_sample
に行き、firebase init
を実行
いくつか質問を聞かれるので、それっぽく答える。
cd C:\firebase_sample
firebase init
Are you ready to proceed?
YES
Which Firebase CLI features do you want to setup for this folder? Press Space to select features, then Enter to confirm your choices
Hostingを選択
Project Setup
先ほどブラウザで作っておいたプロジェクトが出てくるので、選択
What do you want to use as your public directory?
public
Configure as a single-page app (rewrite all urls to /index.html)?
No
##ファイルを作ってFirebaseにアップロード
静的なファイルをアップロードする環境が整ったので、アップロードしてみます。
過去に作った単体で動くブックマークレットを持ってきて、C:\firebase_sample\public\sample.js
として置きました。
javascript:(
function(){
title = document.title;
href = document.location.href;
canonical = get_canonical();
console.log('ページタイトル=%s \n href=%s \n カノニカルURL=%s', title, href, canonical);
alert('ページタイトル:'+title +"\n現在のURL:"+href + "\nカノニカルURL:" + canonical);
function get_canonical(){
links = document.getElementsByTagName("link");
for ( i in links) {
if (links[i].rel) {
if (links[i].rel.toLowerCase() == "canonical") {
return links[i].href;
}
}
}
return "";
}
}
)();
ファイルをアップロードするには、deploy
コマンドです。
C:\firebase_sample
λ firebase deploy
=== Deploying to 'bookmarklet-25d13'...
i deploying hosting
i hosting[bookmarklet-25d13]: beginning deploy...
i hosting[bookmarklet-25d13]: found 3 files in public
+ hosting[bookmarklet-25d13]: file upload complete
i hosting[bookmarklet-25d13]: finalizing version...
+ hosting[bookmarklet-25d13]: version finalized
i hosting[bookmarklet-25d13]: releasing new version...
+ hosting[bookmarklet-25d13]: release complete
+ Deploy complete!
Project Console: https://console.firebase.google.com/project/bookmarklet-25d13/overview
Hosting URL: https://bookmarklet-25d13.firebaseapp.com
終わった。ブラウザでアクセスしてみる。
ファイルのURLは、https://bookmarklet-25d13.firebaseapp.com/sample.js になります。
ちゃんと取れますね。
次は、ブックマークレット経由でsample.js
を取ります。
javascript:(function(d,j,s){s=d.createElement('script');s.src=j;d.body.appendChild(s);})(document,'https://bookmarklet-25d13.firebaseapp.com/sample.js')
当然ながらちゃんと動きました。
ブラウザのブックマークにコードを埋め込まない場合は、closure compilerは不要ですね。そのかわり、ブックマークレットを起動するためにインターネット接続が必要になります。
ここまでなら、作ったブックマークレットのJSファイルをどこに置くかの問題の解決だけです。無理してFirebaseにしなくても、herokuでもCDNのcloudinaryでも同じです。せっかくFirebaseを使うので、ブックマークレットからFirestoreと連携させてみます。
#FireStoreとつないでみる
ブックマークレットのコードからFirestoreに繋げてみます。作るものは、楽天市場の商品検索結果に表示されている商品の情報を刈り取り、Firestoreに放り込むサンプルです。誰トクな感じです。スクレイピングでいいだろ、というのは置いときます。
実行すると、こんな感じで商品の名前、売っている店の名前、各種IDが保存されます。
##Firestoreを使う前準備
Firebase ConsoleのDatabaseへ行きFirestoreを有効にします。Realtime Databaseではないので間違いないように。
Firestore内にrakuten
という名前のコレクションを作っておきます。
##ブックマークレットのコード
楽天の検索結果ページで実行すると、表示されている商品情報がFirestoreにぶっこまれるブックマークレットです。2019年3月のマークアップに従っていますので、HTMLの構造が変わると動かなくなります。
javascript:(function(f){
loadFirebase();
function loadFirebase(){
console.log('start loading firebase.js');
s=document.createElement('script');
s.src='https://www.gstatic.com/firebasejs/5.7.1/firebase.js';
s.onload=loadFirebaseApp;
document.body.appendChild(s);
}
function loadFirebaseApp(s){
console.log('start loading app.js');
s=document.createElement('script');
s.src='https://www.gstatic.com/firebasejs/5.7.1/firebase-app.js';
s.onload=loadFireStore;
document.body.appendChild(s);
}
function loadFireStore(s){
console.log('start loading firebase.js');
s=document.createElement('script');
s.src='https://www.gstatic.com/firebasejs/5.7.1/firebase-firestore.js';
s.onload=loadJquery;
document.body.appendChild(s);
}
function loadJquery(s){
console.log('start loading jquery');
s=document.createElement('script');
s.src='https://code.jquery.com/jquery-3.3.1.slim.min.js';
s.onload=function(){f(jQuery.noConflict(true));};
document.body.appendChild(s);
}
})(
function($){
var config = {
apiKey: "xxx", //change your ID
authDomain: "bookmarklet-25d13.firebaseapp.com",
databaseURL: "https://bookmarklet-25d13.firebaseio.com",
projectId: "bookmarklet-25d13",
storageBucket: "bookmarklet-25d13.appspot.com",
messagingSenderId: "000" //change your ID
};
firebase.initializeApp(config);
var db = firebase.firestore();
const settings = { timestampsInSnapshots: true };
db.settings(settings);
var config=null;
db.collection("configs").doc("rakuten").get()
.then((doc)=>{
config = doc.data();
console.log(config);
}).then(()=>{
showForm();
});
console.log('END');
function showForm(){
form =
'<div>'+
'<button id="_getitem">情報取得</button>'
'</div>';
$('body').append(form);
$('#_getitem').on('click', function(){
db.collection("configs").doc("rakuten").get()
.then((doc)=>{
config = doc.data();
}).then(()=>{
list = getItem();
console.log(config.max_item, list.length);
//ひとつずつ保存
for(var i=0; i<config.max_item && i<list.length; i++){
console.log(i);
saveItemToFirestore(list[i]);
}
//まとめて保存
saveItemsToFirestore(list);
});
});
}
function getItem(){
itemlist =[];
divs = $('div.dui-item.searchresultitem').each(function(i,e){
item={};
item.i = i;
item.id = $(e).data("id");
item.sid = $(e).data("shop-id");
item.item_name = $(e).find("div.content div.description.title h2 a").first().attr('title');
item.shop_name = $(e).find("div.content div.description.merchant a").first().text();
item.price = $('div.content.description.price span').first().text();
console.log(item);
itemlist.push(item);
});
return itemlist;
}
function saveItemToFirestore(item){
db.collection('rakuten').add(item).then(function(docRef) {
console.log("Document written with ID: ", docRef.id);
});
}
function saveItemsToFirestore(itemlist){
var batch = db.batch();
for(i=0; i<config.max_item && i<itemlist.length; i++){
docid = itemlist[i].sid + '-' + itemlist[i].id;
console.log('(%s) > %s', i, docid);
var ref = db.collection("rakuten").doc(docid);
batch.set(ref, itemlist[i]);
}
batch.commit().then(function () {
console.log('success');
});
}
}
)
#コードの解説
##SDK読み込み部分
firebase.js
, firebase-app.js
, firebase-firestore.js
の順に読み込む必要があるようで、野暮ったいコードになってます。やっていることは、loadFirebase()
で作ったscriptタグのonloadにloadFirebaseApp()
を仕掛け、loadFirebaseApp()
で作られるscriptタグのonloadにloadFirestore()
関数を仕掛けているだけです。
最後にjQueryをロードしています。これはfirebaseやfirestoreと全く関係ありません。
##firebaseの初期化部分
var config = {
apiKey: "xxx",
authDomain: "bookmarklet-25d13.firebaseapp.com",
databaseURL: "https://bookmarklet-25d13.firebaseio.com",
projectId: "bookmarklet-25d13",
storageBucket: "bookmarklet-25d13.appspot.com",
messagingSenderId: "000"
};
firebase.initializeApp(config);
var db = firebase.firestore();
const settings = { timestampsInSnapshots: true };
db.settings(settings);
firebaseのwebアプリケーションを作るときと同じようなコードです。自分のfirestoreに繋ぐためのSDKのコードは、コンソールの「アプリを追加」から左から3番目のボタンをクリックして入手します。configデータをfirebase consoleから貼り付けるだけです。TIMESTAMP系のエラーが出るのでsetting
を1つ足しています。
##firebaseからデータの読み込みをする部分
var config=null;
db.collection("configs").doc("rakuten").get()
.then((doc)=> {
config = doc.data();
console.log(config);
}).then(()=>{
showForm();
});
Firestoreからデータを読み込みしてみます。
ブックマークレットだからといって特別なことはなく、普通のfirebaseのWebアプリケーションと同じ仕組みで行います。今回は、検索結果のうち何件を処理するかをfirebase側に定義をし、その値に従ってスクリプトが動作を変えるようにしてあります。このような仕組みを入れておくとブックマークレット内に定数が入らず、コードの修正をしないで動きを変えることができるようになります。
データ取得が終わったら、showForm()
を実行します。
##formを作る処理
function showForm(){
form =
'<div>'+
'<button id="_getitem">情報取得</button>'
'</div>';
$('body').append(form);
$('#_getitem').on('click', function(){
db.collection("configs").doc("rakuten").get()
.then((doc)=>{
config = doc.data();
}).then(()=>{
list = getItem();
console.log(config.max_item, list.length);
//ひとつずつ保存
for(var i=0; i<config.max_item && i<list.length; i++){
console.log(i);
saveItemToFirestore(list[i]);
}
//まとめて保存
saveItemsToFirestore(list);
});
});
}
jQueryを使ってボタンを1つ生成しています。作ったボタンにイベントハンドラを仕掛けておきます。
ボタンを押したときにやることは、
- firebaseのconfigの最新値を読み直す
-
getItem()
で画面のHTMLから情報を抜きだす -
saveItemToFirestore()
で保存する
クリックイベントには骨格だけを定義してあります。具体的な処理は、以降の関数に書いてあります。
##画面からデータを抜き出す部分
jQueryを駆使して取り出しています。firestoreに入れやすいように、item={};
に格納しておきます。
##firestoreに放り込む部分
function saveItemToFirestore(item){
db.collection('rakuten').add(item).then(function(docRef) {
console.log("Document written with ID: ", docRef.id);
});
}
これもチュートリアルレベルの使い方です。collectionに対してadd()
するだけです。javascriptのオブジェクト形式(連想配列形式)でデータをaddに渡せはOKです。firebaseのSDKのおかげでデータの保存は2行で済みます。楽ですね。document(collectionの下の要素)を指定しないで放り込んでいるので、ドキュメントIDは登録時にfirestore側に適当に振られます。
##firestoreに放り込む部分(バッチ)
function saveItemsToFirestore(itemlist){
var batch = db.batch();
for(i=0; i<config.max_item && i<itemlist.length; i++){
docid = itemlist[i].sid + '-' + itemlist[i].id;
console.log('(%s) > %s', i, docid);
var ref = db.collection("rakuten").doc(docid);
batch.set(ref, itemlist[i]);
}
batch.commit().then(function () {
console.log('success');
});
}
こっちは複数のItemを同時に保存する方法です。firestore
のインスタンスからbatch
というインスタンスを作って、そこにデータを預けておいて最後にまとめてcommit()
する使い方です。HTTP/Sの接続数が少ないので効率がよいです。単一の保存と違いdocument idを定義して保存するようにしています。
#実行してみる
firebase consoleでコレクションを削除しておきます。コレクションが無くても、外部から保存すると自動的に作られます。
javascriptのコードをdeployして、ブラウザバーにコードを入れてみます。
カリカリ動いて・・・
こんな感じでデータが保存されています。とっても簡単。
#まとめ
今回はブックマークレットからFirestoreを扱ってみました。
今までもブックマークレットからデータを保存する処理はやっていたのですが、PHPでRESTなAPIを作ってAPI経由でデータを保存していました。それに比べたらAPIを作る手間もなく、SDKが提供されているので保存する処理も簡単にできました。
ブックマークレットとFirebaseを組み合わせるというダレトクな記事ですが、何かのヒントになることを期待しましょう。