※スマホ対応はしてません。

Firebaseのfirestore.indexes.jsonの書式とかインデックスの作り方とかで戸惑った。

カテゴリー: サーバー

先にまとめ

  • たぶん先にウェブコンソールで編集 → 結果を JSON へ保存、という流れ
  • firebase firestore:indexes で「現在設定されているインデックス」をJSONで得られる
  • なので、書式は不明だけど気にする必要なさそう
  • エラーメッセージにインデックス作成 URL が含まれる(のでそれを使う)

ここまでのあらすじ

一覧を新しいもの順にしようと orderBy() 追加したらエラーになった。

const collRef = firebase.firestore().collection(collectionName)
  .where('userId', '==', userId)
  .orderBy('updatedAt', 'desc');
FirebaseError: The query requires an index. You can create it here: https://console.firebase.google.com/project/your-project/database/firestore/indexes?create_composite=…

このクエリーにはインデックスが必要だよ、この URL から作れるよ。とのこと。

インデックスを作成する

firestore.indexes.json が気になるところなんだけど、後回し。

エラーメッセージに含まれる URL を開くと Firestore の Indexes のページが出てきて、「インデックス作るよ!」というダイアログが表示される。

Create composite index というダイアログ。これから作成するインデックスの対象コレクション、フィールドと昇順、降順が確認できる。

素直に “Create index” ボタンを押せばすぐ作ってくれる。

いや、すぐは作ってくれない

すぐ作り始めてはくれるんだけど、なかなか時間がかかる様子。

更新が必要なデータ量によっては、インデックスの作成に数分かかる場合があります。

手元の 10 件ない程度の環境で、完了まで 30 秒くらいにかかった。

インデックスを firestore.indexes.json で管理する

後から環境を再現できるようリポジトリーに設定を持っておきたい。わかる。

firebase init で firestore.indexes.json というファイルを作ってくれるんだけど、どうやらこのJSONファイルをいじるのではなく、先にコンソールで作業してから記録するというのが良さそう。

つまりこんな流れ。

  1. ウェブのコンソールで作業(前節でやった)
  2. firebase で定義のスナップショットを firestore.indexes.json へ保存
  3. 必要に応じて firebase からデプロイして環境を再現

スナップショットを保存

スナップショットという表現で良いのかわかんないけども。

firebase コマンドで現在設定されているものをJSONで得られる。標準出力に出てくるので、> を使ってJSONファイルへ上書き出力してやる。

$ firebase firestore:indexes > firestore.indexes.json

結果、例えばこんな感じであるらしい。

{
  "indexes": [
    {
      "collectionGroup": "rooms",
      "queryScope": "COLLECTION",
      "fields": [
        {
          "fieldPath": "userId",
          "order": "ASCENDING"
        },
        {
          "fieldPath": "updatedAt",
          "order": "DESCENDING"
        }
      ]
    }
  ],
  "fieldOverrides": []
}

デプロイして環境再現

本番環境を更新するとか開発環境を作り直すとかは、JSON ファイルを用意したあとは普通にデプロイするだけ。インデックスだけの場合は --only firestore:indexes で。

$ firebase deploy --only firestore:indexes

最初間違えて単数 firestore:index とやってしまい、でもエラーにならないので気づくのに時間がかかった……。

なお JSON へ追加してデプロイすると作成してくれるけど、JSON で削除してデプロイしても実際に削除してくれない。

コマンド詳細

$ firebase firestore:indexes --help
Usage: firestore:indexes [options]

List indexes in your project's Cloud Firestore database.

Options:
  --pretty    Pretty print. When not specified the indexes are printed in the JSON specification format.
  -h, --help  output usage information

CI でデプロイするとしばらくエラーに

CI から firebase deploy するプロジェクトで試したところ、Firestore の更新も Hosting へのアップロードも同時に行われるため、Firestore のこのインデックス作成完了までの間はエラーになってしまった。なんか対応のしようがあるんだろうか? いったん firebase deploy --only firestore:indexes して、準備完了をどうにかして検知して、それから Hosting を更新するなんてことができる?

削除して再作成

削除はコンソールで Firestore → Indexes の一覧右側 “…” から。すぐ終わる。

終わるが、そのあとすぐに作り直そうとするとエラーになった。

$ firebase deploy --only firestore:indexes

=== Deploying to 'my-project'...

i  deploying firestore
i  firestore: reading indexes from firestore.indexes.json...

Error: HTTP Error: 409, index already exists

たぶんサーバー側の中のどこかでキャッシュみたいなものが残ってるんではなかろうか。知らんけど。

3 分程度間をおいて再実行したら成功した。作成と同様、量によって時間が伸びると思う。

セキュリティルールは先に JSON を書く

じゃあインデックスだけじゃなくてセキュリティルールも先にコンソールでやるのか、と思ったけどどうもそうでもないらしい。

firebase --help で出てくるコマンド一覧には、Firestore 関連のものはこの indexes と delete のふたつだけ。やっぱりコンソールで試して → JSON コピペして保存して → デプロイ、という流れか。

おしまい

参考

更新履歴

  • 2019-06-01 初版
  • 2019-06-10 「CI でデプロイするとしばらくエラーに」追加

Reactで既存HTMLのプロパティを受け取るのにComponentPropsWithRefが使える?

カテゴリー: JavaScript

話題になっているのを全然みかけないんだけど、皆なんかもっと良いやり方やってんの? こういうことしないの?

あとコードハイライトなくてごめんね……。

先にまとめ

  • TypeScript の話
  • 既存 HTML ほとんどそのままのコンポーネントで、本来の props をそのまま受け取りたい場面
  • props: React.ComponentPropsWithRef<'span'> みたいにやれるっぽい
  • ドキュメントには載ってない
  • 定義を追っかけてみたけどたぶんきっと大丈夫そう
  • よくわかってないです

こんな風にして使っている。

export type HtmlProps<T extends ElementType> =
  React.ComponentPropsWithRef<T>;

export type HtmlComponent<T extends ElementType> =
  React.FC<HtmlProps<T>>;

後者は毎回書くのが面倒で追加したけど、ない方がコードは読みやすいかもしれない。

これを使って、特定のクラス名を付けただけのHTML要素を返すコンポーネントの例。

export const NiceButton: HtmlComponent<'button'> = (props) => (
  <button
    {...props}
    className={`NiceButton ${props.className || ''}`}
  />
);
<NiceButton>Niceですね~</NiceButton>

もうちょい複雑なコンポーネントの例。

interface NiceCheckboxProps extends HtmlProps<'input'> {
  label: string;
}

export const NiceCheckbox: React.FC<NiceCheckboxProps> = (props) => {
  const { className = '', label, ...inputProps } = props;
  return (
    <label className={`NiceCheckbox ${className}`}>
      <input {...inputProps} className="NiceCheckbox-input" type="checkbox" />
      <span className="NiceCheckbox-label">{label}</span>
    </label>
  );
};
<NiceCheckbox
  checked={agreed}
  label="ナイスであることに同意します"
  onClick={onAgreeClick}
/>

場面

ほぼ HTML

上に書いた、ああいう「ほとんどただの HTML 要素なんだけどちょっと追加とか組み合わせとかがあって、方々で使うから名前を付けて簡単に使いまわしたい」ような場面です。

CSS フレームワークみたいなノリだと思うんだけど、React でやるからには毎回クラス名書くんじゃなくて、そのクラス名を付けてくれるコンポーネントを使うようにしたいなと。

HTML同様の型が欲しい

例えば <input> だと value を持ってたりするじゃないですか。<span> にはないじゃないですか。そういうのを、補完で出したり型確認で弾いたりしたいなと思っておりました。

見つけた

React.prop まで入力したところの補完で出てきた候補の中からそれっぽいのを眺めてるときに見つけました。

React 公式サイトで検索しても出てこない、と思ったけど React プロジェクトじゃなくて @typed のだから当たり前か。ありがてえ。

定義を追ってみる

ここから先はTS初級者がうんうん唸ってるだけです。誰かに教えてほしい……。

なお @types/jest のバージョンは 24.0.12 でした。

ComponentPropsWithRef

type ComponentPropsWithRef<T extends ElementType> =
    T extends ComponentClass<infer P>
        ? PropsWithoutRef<P> & RefAttributes<InstanceType<T>>
        : PropsWithRef<ComponentProps<T>>;

へ、へえー……。

ElementType

まずジェネリクスの方。

type ElementType<P = any> =
    {
        [K in keyof JSX.IntrinsicElements]: P extends JSX.IntrinsicElements[K] ? K : never
    }[keyof JSX.IntrinsicElements] |
    ComponentType<P>;

これはあれだな、IntrinsicElements にあればその型('a' なら <a> 用の props)を使うぞってことだな。

IntrinsicElements

interface IntrinsicElements {
    // HTML
    a: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>;
    abbr: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
    address: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
    area: React.DetailedHTMLProps<React.AreaHTMLAttributes<HTMLAreaElement>, HTMLAreaElement>;
    article: React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;
…

究極的にはここの右側 React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement> を毎回コピペして使えば使えるんだな。したくないけど。

JSX で書いた <span> とかを VS Code で Ctrl + クリックすると出てくるのもこいつ。

ComponentClass

こっちがわからない。

interface ComponentClass<P = {}, S = ComponentState> extends StaticLifecycle<P, S> {
    new (props: P, context?: any): Component<P, S>;
    propTypes?: WeakValidationMap<P>;
    contextType?: Context<any>;
    contextTypes?: ValidationMap<any>;
    childContextTypes?: ValidationMap<any>;
    defaultProps?: Partial<P>;
    displayName?: string;
}

<infer P> との組み合わせで、React.Component 継承のクラスを与えると良い感じに props の型を抽出してくれる系かなと思ったんだけどそんなことなかった。

ちなみに infer ってここ(上の ComponentPropsWithRef のとこ)で初めて見ました。

PropsWithRef

Indent Hadouken みがある。

/** Ensures that the props do not include string ref, which cannot be forwarded */
type PropsWithRef<P> =
    // Just "P extends { ref?: infer R }" looks sufficient, but R will infer as {} if P is {}.
    'ref' extends keyof P
        ? P extends { ref?: infer R }
            ? string extends R
                ? PropsWithoutRef<P> & { ref?: Exclude<R, string> }
                : P
            : P
        : P;

たぶん大丈夫でしょう

知らんけど。

とりあえず動いてはいる。うーんちゃんと理解して使いたい……。

おしまい

VS Code で F12 を駆使して追っかけてみました。ほんとべんり。

よくわからないけどとりあえず動いているっぽいのでこのまま使おう。趣味プロジェクトだし、まあーそのうちTS力が上がれば完全理解できるでしょう。

あとそういえばこのブログテーマも更新して、TypeScript のハイライトも入れたいなあ。

Edgeでiframe内でウェブフォントが使えないかもしれない問題を踏んだ。

カテゴリー: CSS

最近踏んだ問題です。

  • Edge
  • 複数の <iframe>
  • フレームは同時に生成
  • 同じウェブフォントを読み込む
  • 一部のフレームでしかウェブフォントが出てこない

デモとコードはこちら。

3つのフレームのうちひとつでしかウェブフォントが有効化されてない様子。

EdgeのDevToolsでネットワーク見てると、CSSファイルは読み込まれているけどフォントファイルは読み込まれていない、という状況までは観測できた。

解決策

どうやらCSSファイルの読み込み完了を待ってから、そのウェブフォントを利用する部分を出力すると問題なくなる様子。

つまり onload で待つべしと。

const elLink = document.createElement('link');
elLink.href = `https://fonts.googleapis.com/css?family=${family}`;
elLink.rel = 'stylesheet';

elLink.onload = () => render(); // <---

document.body.appendChild(elLink);

原因はっきりわかってないけどこれでうまく行っているように見える。

処理の想像。

  1. フレーム1出力開始
    1. CSSファイル読み込み開始
    2. ウェブフォント利用のテキスト出力
    3. 該当フォントなしとして記憶 (A-1)
  2. フレーム2出力開始
    1. CSSファイル読み込み開始
    2. ウェブフォント利用のテキスト出力
    3. 該当フォントなしとして記憶 (A-2)
  3. いずれかのフレームで
    1. CSSファイル読み込み完了 (B)
    2. 該当なしのフォントに合致、フォントファイル読み込み開始 (C)
    3. フォントファイル読み込み完了
    4. テキスト再描画

このB前にAが来るとCがちゃんと動いてない気がします。知らんけど。

Edge…

老い先短いEdgeHTMLさんのためにどれだけコストかけるべきか悩む。

おまけ

おまけ2

Google Chromeで音声認識する例。

カテゴリー: JavaScript

意識しないとすぐTwitterに書いて終わりにしてしまうので、気負わず軽い気持ちで書きます。

諸事情により音声認識APIを試しているんだけど、ウェブ標準でもそういうAPIが準備中であるのに気づきました。現状のGoogle Chromeでもそこそこ動くようです。せっかくなので(Chromeで)動くやつを用意してみました。

ginpei/speech-recognition-example

まだ実験的な機能だし、実際動きも微妙なところなので、やっぱりGoogleなりMicrosoft AzureなりのAPI使う方が良さそう。無料枠あるし。

基本的なコード

// 作成
const sr = new window.SpeechRecognition();

// 完了時に結果を表示
sr.addEventListener('result', (event) => {
  const text = event.results[0][0].transcript;
  console.log(text);
});

// 開始
sr.start();

音が止まると音声認識も自動で終了する様子。

各ブラウザー

Chrome

接頭辞付きの webkitSpeechRecognition で動く。まだ完璧ではない様子。

あとそういえば昔 <input x-webkit-speech> みたいなのあったなあ、みたいな記憶。

Firefox

まだ対応していない。コードは mozSpeechRecognition に対応しておいたので、実装されたらきっと動くことでしょう。知らんけど。

Edge

ちょうど最近開発版が出た、中身がChromiumの新Edgeはまだ動きが微妙な感じでした。

同じ webkit 接頭辞なのに動いたり動かなかったりは困るなあ。いや今は開発版だから良いんだけど。

Safari

試してないけど未対応らしい。

JPEG/Exif画像の回転情報を取れるやつ作った。

カテゴリー: JavaScript

新年明けましておめでとうございます。作ったよ。

インストール。

$ npm install @ginpei/exif-orientation

利用例。

import * as exif from '@ginpei/exif-orientation';
 
const orientation = await exif.getOrientation(fileOrBuffer);
console.log(
  `${orientation.rotation} degree,`,
  orientation.flipped ? 'flipped' : 'not flipped',
);

上記のものではないが実行例。

$ node from-node.js sample-images/090-flipped-5.jpg
Read image from: sample-images/090-flipped-5.jpg
Rotation: 90 degree, Flipped: true.

検索するといかにもな名前のやつとかが複数出てくるんだけど、先行事例に対する利点は特にないです、たぶん。

作りたいので作った感じのやつです。ちゃんと動くと思うけど。

Scoped package

名前が取られてたけど作っちゃったし変えるのもなあと思って、自分の名前 “@ginpei” 付きの形での公開としました。

READMEにも書いたけど、TypeScriptの場合は --moduleResolution node の設定が必要です。へえそうなんだ、知らなかった。

エラー例。

src/index.ts:1:23 - error TS2307: Cannot find module '@ginpei/exif-orientation'.

1 import * as exif from '@ginpei/exif-orientation';
                        ~~~~~~~~~~~~~~~~~~~~~~~~~~


Found 1 error.

Exif仕様

こちらのPDFを大いに参考にしています。感謝感謝。

ところでExifて日本発祥の規格なんだね。

富士フイルムが開発し、当時の日本電子工業振興協会 (JEIDA)で規格化

画像の回転

とかそういうのは、一切しないです。調査するまで。

やるならCanvasが良いのかな。いやJPEGの画素情報の位置を回転的に動かす方が再圧縮しないから綺麗そう。

一時的ならCSSでやるのも手。よし transform: matrix() 使おうぜ!

JPEG以外

Exifを他の規格の画像にも使えるらしいんだけど、JPEGのみの対応です。見ないし。

おしまい

本年もよろしくお願い致します。