銀の弾丸

プログラミングに関して、いろいろ書き残していければと思っております。

ウェブアプリからGoogle Driveのファイルを扱う 'gdrive-fs'

f:id:takamints:20171130164857j:plain
photo credit: suzyhazelwood DSC04299-02 via photopin (license)

ウェブアプリから Google Drive のファイルを操作するモジュール(npm)を公開しましたので、ご紹介。

これからWebをはじめる人のHTML&CSS、JavaScriptのきほんのきほん
たにぐちまこと
マイナビ出版 (2017-03-27)
売り上げランキング: 13,953

npmはこちら。

www.npmjs.com

目次

関連記事:

takamints.hatenablog.jp

takamints.hatenablog.jp

機能概要

サインインしたユーザーのファイルを読み書き

このモジュールを利用するウェブアプリで、ユーザーがGoogleアカウントでサインインすると、ウェブアプリからユーザー自身のGoogle Driveのファイルへアクセスできます。

これらのファイルは、ユーザーが特別に共有設定などをしない限り、他人には見ることができません(ただしアプリはDrive内の全内容を把握できることには注意が必要)。

ファイル名によるファイルの操作

ローカルのファイルシステムと同じように、ファイル名とフォルダ名によってファイルを扱うAPIを提供しています。

Google Drive API は、ファイルIDによってファイルにアクセスしますが、パスでアクセスできるほうが直感的ですよね。

APIは、おおまかに Node.jsの fs モジュールのメソッド名に従っています。 ただし、多くが非同期関数として動作しますが、callback は受け付けず、Promiseを返します(ほとんどが async 関数です)。

UI作成支援機能

こちらはまだα版。動作確認用のサンプル実装程度です。

そのうちまじめに作って公開するつもりなのでちょっと待ってネ。

インストールと使い方

npmで公開しているので、npmでinstallしてください。

$ npm install --save gdrive-fs

バンドラーを使われているなら、CommonJS的にインポートしてください。

const Gdfs = require("gdrive-fs");
(async()=>{
    await Gdfs.loadApi(<clientId>, <apiKey>);
    .
    .
    .
})();

バンドラーを使っていない場合、HTMLのScriptタグで 本モジュールの/build/gdrive-fs.min.jsを読み込んでください。 この場合、グローバル変数として、Gdfsが利用できます。

<!DOCTYPE html>
<html>
<head>...</head>
<body>
.
.
.
<script src="<path-to-module>/build/gdrive-fs.min.js">
(async()=>{
    await Gdfs.loadApi(<clientId>, <apiKey>);
    .
    .
    .
})();
</script>
</body>
</html>

動作条件

APIキー」と「クライアント ID」

ウェブアプリ開発者は、Google Developer Console でプロジェクトを作成し、ウェブアプリケーション向けの認証情報として、「APIキー」と「OAuth 2.0のクライアント ID」を作成し、適切に設定しておく必要があります。

多くの場合、APIキーはHTTPリファラーによる制限をかけ、クライアントIDのタイプは「ウェブアプリケーション」としておきます。

APIリファレンス

本モジュールが提供しているクラス、APIの完全な説明は、以下のリンクからどうぞ。 つたない英文で書いています。

以下に要約した内容を転載します。最新情報は上記URLを参照してください。

Gdfsクラス

このクラスは Google Drive API v3 へのインターフェースであり、インスタンスはカレントディレクトリを管理して、ファイルやフォルダを操作するメソッドを提供します。

このインスタンスを作成する前に、これを利用するアプリケーションの Client Idと Api Keyにより、クラスメソッド loadApi で、このAPI群が読み込まれていなければなりません。 これら2つのキーは、Google Developer Consoleのプロジェクトで作成されていなくてはなりません。

また、ファイルを操作するにはユーザーがGoogleアカウントでサインインしておく必要があります。

このインスタンスのカレントディレクトリは、コンストラクタでルートフォルダに初期化されます。 これはchdirメソッドで変更できます。 カレントディレクトリ変更時には、oncwdupdate コールバックが呼ばれます。カレントディレクトリを知るには、cwd メソッドが利用できます。

クラスメソッド

  • async loadApi(clientId, apiKey) - Google Drive API (v3)を読み込む。
  • isSignedIn() - Google Driveにサインインしているかどうかの確認。
  • async signIn() - Google Driveにサインインする。
  • async signOut() - Google Driveからサインアウトする。

コンストラク

  • Gdfs() - コンストラク

インスタンスメソッド

  • async chdir(directory:string) - カレントディレクトリの移動。
  • cwd() - カレントディレクトリを返す。
  • async isDirectory(path:string) - パスがディレクトリかどうかを返す。
  • async mkdir(path:string) - ディレクトリの作成。
  • async readdir(path:string, options:object) - ディレクトリ内のファイルのリストを読み出す。
  • async readFile(path:string) - ファイル内容の読み取り。
  • async rmdir(path:string) - ディレクトリ削除。
  • async stat(path:string) - ファイルのメタ情報を取得する。
  • async unlink(path:string) - ファイルの削除(ゴミ箱には入らない)
  • async writeFile(path:string, mimeType:string, data:any) - ファイルへの書き込み。または新規作成。

制限事項・注意事項

名前だけではファイルを特定できない問題は無視

Google Driveでは、一つのフォルダ内に同じ名前のファイルが複数存在できます。 なので、本当は名前だけではファイルを一意に特定できません。 でも、そういう使い方自体が混乱のもとですから、本モジュールではバッサリ無視して最初に見つけたほうを採用しています(多分)。

削除してもゴミ箱には入りません

Gdfs.unlink(path) メソッド で削除した場合、ごみ箱には入りませんので、重々気を付けてくださいね。

セキュリティ警告とアプリケーションの申請について

未申請のアプリケーションに、ユーザーがサインインしようとすると「安全でないアプリケーションである」との警告が表示されます。 Googleにアプリケーションを申請して承認されていれば、これは表示されなくなるはずです。 アプリケーションを正式に公開する場合は申請しておいたほうが良いでしょう。

アプリケーションの申請はそれぞれのエンドポイントで行う必要があるため、本モジュールで行うものではありません。

ライセンス

本モジュールはMITライセンスにおいて公開しています。

免責

本モジュールは、これを利用するアプリケーションの安全性を保証せず、その動作について一切の責任を負いません。 本モジュールを利用するアプリケーションのすべての動作は、アプリケーションの製造者の責任において試験され、ユーザーに提供されるべきです。 悪意のあるアプリケーションや不具合によって被害を受けないためには、信頼できる人以外が作ったアプリケーションには接続するべきではありません。

SVGでツールチップを表示する

f:id:takamints:20181014113409p:plain

WEBページのインラインSVGツールチップを表示させる方法は、HTMLとは違うので、以下に両方の方法を書いています。

ツールチップは、画面の要素にマウスポインタを乗せてしばらくすると、説明文などが表示される小さなボックスのことですね。

ホームメイド 製菓用チョコチップ160g
共立食品
売り上げランキング: 114,017

HTMLの場合

HTMLでは「title属性(Attribute)」です。 title属性に表示したい内容を設定しておけば、マウスポインタを乗せてしばらくすると、ツールチップが表示されます。

以下のHTMLで描いたボックスにマウスを乗せると・・・

HTMLでは「title属性(Attribute)」に設定します。

ソース:

<span style="border-radius:5px; background-color: #080; color:white; font-weight:bold; padding: 1em 2em;"
class="tooltip-container"
title="これはTITLE属性の内容です。">
HTMLでは「title属性(Attribute)」に設定します。
</span>

SVGの場合

しかし、SVGにはtitle属性がありません。 「じゃどうするか?」って調べてみると「SVGでは『title要素』を使いましょう」とのことでした。

以下のインラインSVGで描いた図形にマウスを乗せるとSVGツールチップが表示されます。

これはSVGのタイトル要素。 SVGではTITLE要素に設定しますの・・・

ソース:

<svg viewport="0 0 400 100" width="100%">
  <g>
    <title>これはSVGのタイトル要素。</title>
    <rect x="0" y="0" width="400" height="100" fill="#080" rx="5" ry="5"/>
    <rect x="30" y="20" width="50" height="50" fill="#008" rx="5" ry="5"/>
    <rect x="70" y="45" width="30" height="30" fill="#088" rx="5" ry="5"/>
    <rect x="20" y="50" width="40" height="40" fill="#800" rx="5" ry="5"/>
    <circle cx="340" cy="75" r="20" fill="#880"/>
    <circle cx="310" cy="40" r="40" fill="#808"/>
    <circle cx="350" cy="32" r="30" fill="#088"/>
    <text x="25" y="80" width="400" fill="white" font-weight="bold"
        >SVGではTITLE要素に設定しますの・・・</text>
  </g>
</svg>

JavaScriptではツールチップの内容は、title要素のtextContentに設定します。


ParcelのBabelでAsync関数を変換せずに使う設定

f:id:takamints:20180907072243p:plain


バベル消滅 (角川文庫)
KADOKAWA / 角川書店 (2015-10-24)
売り上げランキング: 228,520

前回記事で、ParselでAsync関数が普通の関数に変換されてしまって困っていましたが回避する方法が分かりました(実は困らなくてよかったのですが、それは後ほど)。

単にワタシが知らないだけで、普通にBabelの設定でした。 Parcelはまったく無関係。 まあ誰でも最初はビギナーですし。 いくつになってもお勉強。

前回の記事:

takamints.hatenablog.jp

目次

最新ブラウザをターゲットに指定するだけ

以下の .babelrc を使えばAsync関数は無変換でそのまま使用されますね。

.babelrc

{
    "presets": [
        ["env", {
            "targets": {
                "browsers": ["last 1 Chrome version"]
            }
        }]
    ]
}

しかし、これって「ターゲットブラウザはChromeの最新版」ってだけの設定ですよね。 Chromeの最新版が Async/Await に対応しているからってことなのでしょう。

Async/Await対応ブラウザ全部乗せ

他のブラウザ、FirefoxやEdgeでもちゃんと動作するのか不安な場合は、以下のようにAsync/Await対応のブラウザを全部並べておけばよいはずです。 そうすることで各ブラウザ間の差も吸収してくれるはず(今やほとんど存在しないかもしれませんが)。 ちなみに、ここにIEを入れるとAsync/Awaitに非対応ですからPromiseを使った処理に変換されます。

.babelrc

{
    "presets": [
        ["env", {
            "targets": {
                "browsers": [
                    "last 1 Chrome version",
                    "last 1 Firefox version",
                    "last 1 Edge version",
                    "last 1 Safari version",
                    "last 1 Opera version"
                ]
            }
        }]
    ]
}

Babel関係のモジュールも不要になります

前回の記事で必要と書いていた babel関係の2つのモジュールも、上の設定では一切不要になりますのでアンインストールして大丈夫。

$ npm uninstall --save-dev babel-runtime babel-plugin-transform-runtime

Polyfillされても困る必要はなかったのだ

なんでAsync関数が普通の関数に変換されて困っていたかというと、関数が非同期であるかどうかを、関数のコンストラクタで判定しようとしていたからなのですが、そもそもそれが間違いでした。

関数オブジェクトのコンストラクタがAsyncFunctionFunction かは同期/非同期に無関係ではありませんが、正しい判定はできません。 Asyncでない普通の関数がPromiseを返してもよいのですから。

関数が非同期かどうかの判定は、その関数を実際に呼び出して「戻り値がPromiseであるかどうかで判断すべき」なんですね。

つまり、今さらですけど、コンストラクタで困る必要はありませんでした。 大騒ぎして失礼こきました。

参考サイト

設定なしのバンドラー「Parcel」がWeb開発には最強の予感!だがしかし(期待を込めて)・・・

f:id:takamints:20180904092537p:plain


家族がよろこぶ ダンボール工作
pika
日本ヴォーグ社
売り上げランキング: 84,363

昨年末あたりから、よく聞くようになった「Parcel」を使いはじめました。

敷居が低くて簡単・便利。でも不安定な動作や実行時エラーに引っかかったりもしましたので、以下に紹介がてら対処法なども書いておきます。

現時点で、自分の知識不足や経験不足に起因する点があるかもしれません。 以下、おかしなことを書いていたら、コメント等で御教示ください。 よろしくお願いいたします m(_ _)m。

Parcelって?

「Parcel」は、最近のWebアプリの開発でほぼ必須ツールと言ってよいバンドラー(およびタスクランナー)ですね。 なにが凄いって、常識破りの売り文句「設定不要」というところでしょう。

バンドラーやタスクランナーの設定しんどい

ワタシはこれまで Grunt+Browserify+Uglify でやっていまして、特にこだわりはないのですが、既に設定が固まっていて、「Grunt遅いよ」「Gulpが速いよ」「WebPackのほうが便利でしょ」などと聞いても、腰が重くて動じない。 新たなツールの新たな設定を覚えるのって邪魔くさいでしょ。そういうのが好きな人がいるのも知っているけどワタシ自身はそうではない。

Parcelは設定不要。何度も言うよ~♪「設定不要」

そんなときに「Parcel は設定不要ですよー」と知って「それならば」と、新規のnpmで使ってみたら「めっちゃ便利ですやんコレ!スゲー!」となったわけです。 前述のように、まだ安定しない点もあるようですが、今後どんどん便利になっていくことを期待しつつてな感じで、以下どうぞー。

https://parceljs.org/

※ ちなみに本記事執筆時点のワタシの環境はこちらになります:

  • OS: Windows 10 / MSYS2
  • node: 8.10.0
  • npm: 5.6.0
  • devDependencies(parcel関連の一部)
    • parcel-bundler: 1.9.7
    • babel-plugin-transform-runtime; 6.23.0
    • babel-runtime: 6.26.0

目次

Parcelの凄いところ

Parcelの凄いところは、すでにたくさん紹介サイトがありますので、ここでは箇条書きにしておきます。 これ以外にもたくさんあると思います。

  • 本当に設定不要(疑ってたわけじゃないけどホントに設定不要で驚いた)
  • ソースを更新すると自動でビルド。
  • HTTPサーバーが立ち上がっていて、ビルド後モジュールが置き換えられる(HMR - Hot Module Replacement)。
  • ビルドはかなり速い気がする。2回目以降はキャッシュを使ってるんですよね。初回も速い気がしますけど。
  • 最初からTypeScriptに対応している。

Parcelのいまいちなところ

決してダメだっつってんじゃありませんよ。 今後の改善に期待しつつ、ハマったことなどを書いています。 また、自分の開発環境に依存するところもあるのかも。

自動ビルドがおかしくなることがある。

困り事

長く自動ビルドを繰り返していると、ソースの変更が正しくビルド結果に反映されていないと感じることが何度かありました。 Parcelはキャッシュを利用して高速化を図っているようですが、その辺に何かあるんだと思います。

対処法

Parcelを強制停止して、出力ディレクトリと、キャッシュディレクトリを消去して再度Parcelを立ち上げれば正しい結果が得られました。

$ rm -r dist/ .cache/

ビルドエラーで自動ビルドが止まっちゃう?

困り事

複数ファイルを編集していて、ひとつのファイルを先に保存した時点では整合が取れていなくてエラーになっちゃうことがありますよね。 このとき、当然ビルドは失敗して、HMRでブラウザにエラーが表示されます。 しかし、ソースファイルをキチンと修正しても、ビルドが走ってくれません。

対処法

こちらも、とりあえずParcelを再起動すれば治ります。 それでもおかしいと感じる場合、 rm -r dist .cache で出力ファイルとビルドキャッシュを消去して再起動ですね。

HMRがうまく動かないことがあります

困り事

どういう状況なのか詳細はまだわかっていないのですが、HMRが動いたタイミングで、ブラウザで延々エラーログが吐かれる状態になったりします。 単純にページをリロードしてくれるとよいのですけど。 それだと再読み込みに時間がかかるのかもしれませんが、動かないよりマシじゃないかと。

対処法

Webページをリロードします。大抵これでなんとかなりますが、それでもだめなら Parcelを再起動しましょう。 必要ならば出力ファイルとキャッシュクリア(ここまですることはないと思います)。

Asyncを使っていると実行時にエラーが出る

困り事

Asyncを使っていると、実行時に regeneratorRuntime is not defined というエラーが発生します。

対処法

エラーメッセージでググったらGitHubIssue#871 に解決法が載っていました。

以下のコマンドで、npmにbabelのランタイム?と変換プラグイン?をインストールし、

$ npm install --save-dev babel-plugin-transform-runtime babel-runtime

モジュールのルートに以下のbabelの設定ファイル? .babelrc を追加すればとりあえずエラーはなくなります。

{
    "plugins": [
        ["transform-runtime",
        {
            "polyfill": false,
            "regenerator": true
        }]
    ]
}

しかしワタシ Babel に詳しくないので、今のところ何がどうなってるのか理解していません(Regeneratorってなんでしょう?)。

そもそもAsync/Awaitをそのまま使うって選択肢はないのかな。

Babelのせい?でAsync関数がAsyncFunctionでなくなっちゃう

困り事

上の .babelrc でAsyncが使えるようになりましたけど、実行時にAsync関数の constructor.name が Function のままなんです。 通常なら async宣言された関数の constructor.name は、 AsyncFunction のはずなのですが。

何が困るってコールバックを呼び出すときに「 AsyncFunction なら Async関数でラップして実行をawaitしたい」のですが、全部Functionになっているので同期できないんですよね。

対処法

こちら、今のところ解決法が見つかっていません。 ParcelではなくBabelの問題だと思っていますが。

Parcelの使い方

parcelをnpmにインストール

npmで使う場合、リポジトリ内で、npm install --save-dev parcel-bundler でインストールして、 package.json の scripts に parcel を登録(以下参照)しておきます。

こうしておけばこのnpmの内部では npm run parcel <パラメータ> でParcelを実行できます。 グローバルにインストールすれば parcel だけで実行できますが、npmの場合は、依存関係が明記されているほうが親切ですよね。

package.json

  ...
  "scripts": {
    "parcel": "parcel",
    "test": "mocha"
  }
  ...

ビルドする

例えば index.html をビルドするには、コマンドラインから以下のように実行するだけです。 (何がビルド対象かを明示するためにscriptsにindex.htmlまで指定したコマンドも登録しておくほうが良いかもしれません)。

$ npm run parcel index.html

> package-name@1.0.0 parcel path/to/repo
> parcel "index.html"

Server running at http://localhost:1234
√  Built in 4.39s.

ビルド結果は ./dist/ 以下に出力されます。 ビルドの前にWebサーバーが起動しており、http://localhost:1234/index.html をブラウザで開けます。 ビルド後もparcelは動き続けており、ソースファイルが更新されると自動的にビルドされて、HMRでブラウザのアプリも更新されます。

あとがき

結局「設定なし」だけケチが付かず、もろ手を挙げて「最強!」とは言えない感じでもあります。 でもやっぱり設定ファイルを書いていないという点は、それだけでも最強といえるかも。 安定すれば「これしかないっ」て言いきっちゃうぞ。

ホント、現時点でもWebアプリの開発では使わない手はないと思います。 npm scriptsとのシンプルな構成が便利かな。

ワタシ自身、まだ使い始めでよくわかっていない点もあります。 他にもCSSの統合なども便利なようで、継続して追いかけていきたいと思っております。 今後、あっちゅう間に使いやすくなっていくんじゃないかな?

今後JavaScriptでArray.forEachはほとんど使わなくなるのかも


いままでJavaScriptの繰り返しは Array#forEachを使いまくっていましたけど、2018年現在「インデックスが必要ないなら for..of 構文が使えますよ」と聞きまして、ミーハーなので突然使い始めています。

聞いたことはありました

いや、for..of については小耳にはさんだことはありましたが「(どうせ)各ブラウザではまだ対応状況がまちまちだったりするんでしょ?」とか思い込んでいて、とりあえず静観してたら忘れちゃっていました(「どうせ」は良くない接頭語w)。

Edge以外はほとんど対応しているみたい

今調べてみると、思った以上に各ブラウザが対応していて、これは無視できないムーヴメントだと認識するに至りました。

MDNによるとEdgeが未対応らしいですね。だから、あまねく世に繰り出すWebサービスでは使えないのかもしれないけれど、個人的にはEdgeはほとんど使わないので、今後for..ofをガンガン使って行こうという所存です。

for ... of の構文は?

MDNでは、以下のように説明されています。

for (variable of iterable) {
  statement
}
variable
それぞれの反復処理において、別々のプロパティの値が variable に代入されます。
iterable
列挙可能なプロパティに対して、反復処理を行うオブジェクトです。

iterableは簡単に言えば配列みたいなオブジェクトで、関数の引数リストである arguments や、DOMの Node.childNodesiterableiterable === Arrayではありませんから、Array#forEachとかは使えなくて、Array.fromArrayに変換しなくてはなりません。 そういう意味では、言語の構文として for..ofがあるので、便利になったと言えるでしょう。 ちなみにStringiterableだそうで、実は本日知りました(内緒)。

一般的な使い方

一般的には以下のように使います。これは配列の場合ですね。他も似たり寄ったりです(多分)。

for(const foo of [0,1,2]) {
    console.log(foo);
}
// output:
// 0
// 1
// 2

速度に関する考察

入力情報によれば「Array.forEachよりも関数呼び出しが伴わない分、for..of のほうが若干速い」とのことでした。

Array.forEachで各ループを別スレッドに振り分けるような実装がされたら、処理速度は比較にならないのでは?と思っていましたが、for..of だって別スレッドに割り振れるはずなので、どっこいどっこいなのかな?と。

ほとんどforEachは使わなくて済みそうですね

見た目に for..ofのほうがスッキリするし若干速そうなので、今後はfor..inを使いましょうと。 インデックスが必要ない場合だけですけどね。