Google Map APIの高度なマーカーのクリックイベントに関するリファレンスが間違ってる

下記はリファレンスのイベントから抜粋したもの。

イベント 説明
click (前略) addEventListener() では使用できません(代わりに gmp-click を使用してください)。
gmp-click (前略) (addListener() ではなく)addEventListener() で使用することをおすすめします。

これを見ると addEventListener('gmp-click') とするのが正しそうだが、これは動かない。
ドキュメントを見ると addListener('click')addListener('gmp-click') で良さそう。(欲しい引数の型でどちらか好きな方を選ぶ)

Docker for Macを捨てcolimaでやっていく

Docer for Macの問題点としてはとにかくI/Oが遅い点です。
nuxt build などフロントエンドのビルドをDockerマシーン上で行うと非常に遅く、プロジェクトの規模とマシンスペックによっては30分以上かかるケースがあります。

また、Docker for Macが有料化されたこともあり、Docker for Macを避ける会社も出てきているかと思います。

Colimaとは

ColimaLimaをラップしたコンテナランタイムで、最小の設定でLimaを通してdockerなどのコンテナを利用することができます。

そもそもLimaはWindowsにおけるWSL2のように、Linuxの仮想環境をMac上で実行するものです。Limaを使ってdockerを利用することもできますが、多くの設定をする必要があり煩雑でした。

Colimaを使うことで、仮想Linux環境上でDockerを利用することができ、多くの場合Docker for Macを利用するよりも高速に、特にI/Oについては大幅な改善が見られます。

Colimaのインストール方法については公式ドキュメントに任せますが、特殊な環境ということもありいくつかのtipsをまとめてみました。

Dockerコンテナ内からDNSが引けずにnpm installやcomposer installなどが失敗する

colimaで作成したdocker環境ではホストのDNSが引けず、DNS解決に失敗することがあります。たとえば npm install や composer install をした場合に「リポジトリが見つからないのでインストールに失敗した」というようなことがあります。

この場合は、下記のようにColimaの設定で明示的にDNSのIPを指定することで解決します。ここではパブリックDNSを指定する必要があります。(Google Public DNSの 8.8.8.8 など)

colima start --dns 8.8.8.8

Rosetta 2を利用する

ARM対応していないDockerイメージを利用することも多々あるかと思います。

ARM MacかつMacOS 13 (Ventura)以降であれば下記のオプションでRosetta 2が利用できます。(Rosetta 2の対応自体はLimaにて行われいます)

colima start --arch aarch64 --vm-type=vz --vz-rosetta

Docker環境がうんともすんとも言わなくなる

nuxt devなどを利用中にHMRが実行されなくなったり、レスポンスが全く返ってこなくなったり、することがあります。この場合、ColimaかLimaがハングしている場合があります。

columa restart をしDockerコンテナを立ち上げ直すことで解決します。

最後に

Colimaを利用することでnuxt buildの時間が半分以下になるケースもありました。よいDXの為には高速な開発環境が必要です。是非Colimaを利用してみてください。

LINE公式アカウントの消せないリッチメニューを消す方法

LINE公式アカウントのチャンネルを運用するには、LINE Official Account Manager(以下 Account Manager)を使う方法とサードパーティーや独自開発をしたアプリケーションを介してLINE Messaging API(以下 Messaging API)を利用する方法があります。
リッチメニューの仕様として、Account Managerで作成したリッチメニューをMessaging APIで操作したり、Messaging APIで作成したリッチメニューをAccount Managerで操作したりすることはできません。

ここで問題になるのが、リッチメニューの表示優先順位です。

ドキュメントによると(1. が最優先)

  1. Messaging APIで設定するユーザー単位のリッチメニュー
  2. Messaging APIで設定するデフォルトのリッチメニュー
  3. Account Managerで設定するデフォルトのリッチメニュー

という順で表示されるのですが、1. や 2. が設定されていると、Account Managerでリッチメニューを作成してもユーザーに表示されませんが、前述の通りAcocunt ManagerからはMessaging APIで作成したリッチメニューを削除することができません。

この場合Messaging APIを利用してリッチメニューを削除する必要がありますが、意外と簡単に削除することができます。

流れとしては

  • チャンネルアクセストークンを用意する
  • リッチメニューIDを取得する
  • リッチメニューIDを指定して削除する

の3段階です。

チャンネルアクセストークンを用意する

Messaging APIを利用するにはチャンネルアクセストークンが必要です。

チャンネルアクセストークンは LINE Developers のコンソール の「チャンネル設定」から確認することができます。

チャンネルアクセストークンがない場合は、Account Managerの右端の「設定」→左メニューの「Messaging API」のページのチャンネルID(Channel ID)とチャンネルシークレット(Channel secret)を確認できます。

そして、短期のチャネルアクセストークンを発行するAPIからチャンネルアクセストークンを発行できます。

リッチメニューIDを取得する

リッチメニューの配列を取得するAPIでMessaging APIで作成したリッチメニューのIDを取得できます。

{
  "richmenus": [
    {
      "richMenuId": "{richMenuId}", // <- これがリッチメニューID
      "name": "Nice rich menu",
      "size": {
        "width": 2500,
        "height": 1686
      },
      "chatBarText": "Tap to open",
      "selected": false,
      "areas": [
        {
          "bounds": {
            "x": 0,
            "y": 0,
            "width": 2500,
            "height": 1686
          },
          "action": {
            "type": "postback",
            "data": "action=buy&itemid=123"
          }
        }
      ]
    }
  ]
}

リッチメニューを削除する

リッチメニューを削除するAPIに先程取得したリッチメニューIDを指定して削除します。

このAPIを叩くことで「Messaging APIで設定するユーザー単位のリッチメニュー」や「Messaging APIで設定するデフォルトのリッチメニュー」も削除することができるので、Account Managerで設定したリッチメニューをユーザーに表示させることができます。

まとめ

Messaging APIが使えると、LINE公式アカウントのチャンネルをより深くカスタマイズできるようになりますが、相応の専門知識が必要です。

もしMessaging APIでお困りごとがあれば、弊社お問い合わせフォームから気軽にご相談ください。

ColimaのDocker内で `composer install` に失敗する問題の解決

M1やM2などのArm Macで、かつ Colima でDockerを使っている場合、Docker環境内で composer install でするとコケる事がある。
厄介なのは、コケたりコケなかったりColimaごと固まったり非常に不安定なことがある。
その場合恐らく、デフォルトの qemu 環境で立ち上がっていると思われるので、一旦VM環境を削除した後に

colima start --vm-type vz --vz-rosetta --mount-type virtiofs

のようにVZ環境で作り直すとうまくいく。

Virtualization.framework(vz)はMac OS 13から利用できる仮想環境のAPIで、I/Oも早くなるらしい。

MacでThunderboltブリッジ接続のネットの上りが遅いのを解決する

tl;dr

下記コマンドでTCP Segmentation Offload を無効にする。

sudo sysctl -w net.inet.tcp.tso=0

説明

(以下すべてmacOS Catalinaで検証しています。)
Mac同士をThunderbolt3ケーブルなどを繋いでThunberboltブリッジ接続しネットワーク共有をした場合に、アップロードの速度が1MB/s以下と異常に遅いのですが、それを解決する方法を見つけました。

Thunderbolt Ethernet Bridge very slow - Apple Community に答えが載っていました。
確かに下記2種類のコマンドのどちらかで解決しました。

sudo sysctl -w net.inet.tcp.tso=0
sudo sysctl -w net.link.generic.system.hwcksum_tx=0

後者の場合はファイルの書き込み速度が50%ほど遅くなる副作用がありましたので、前者をオススメします。

これらは実際のところ何の設定を変えるコマンドなのでしょうか?

我々はその謎を探るべくアマゾンの奥地へと向かった…

net.inet.tcp.tso=0 はTCP Segmentation Offload を無効にしています。
一方 net.link.generic.system.hwcksum_tx=0 は TCP Checksum Offload を無効にしています。

ではなぜ、TCP Segmentation Offloadを無効にしたりTCP Checksum Offloadを無効にしないと異常にネットの上りが遅いのか?
TCP Checksum Offloadを無効にすると書き込み速度が落ちるのか?

答えはわかりませんでした。

でも自分が何をしたか全く分からないよりはマシなので「TCP Segmentation Offloadを無効にした」とだけ覚えておくことにします。

sass-loaderとdart-sassにまつわるfibersの話

tl;dr

用語について

現在npmで sass として公開されているパッケージはdart製のsassとなっています。旧来使われていたnode製のsassは node-sass として公開されています。

ここでは区別を分かりやすくするため、dart製のsassを dart-sass、 node製のsassを node-sass と呼び分けています。

本文

そもそもsass-loaderで何を設定しているのでしょうか。sass-loaderでdart-sassを利用する際はだいたい下記のように設定しているかと思います。

// webpack.config.js
module.exports = {
  // ...
  module: {
    rules: [
      {
        // ...
        use: [
          `// ...
          {
            loader: `sass-loader`,
            options: {
              implementation: require('sass'), // ここで `dart-sass` を読み込んでいる
              sassOptions: {
                fiber: require(`fibers`), // 大体の人がなんとなく一緒に読み込んでいる
              },
            },
          },
        ],
      },
    ],
  },
  // ...
};

まず implementation プロパティについてはsass-loaderのドキュメント に書いてあるとおりですが簡単にまとめると

  • implementation を設定しない場合、 node-sassとdart-sassの両方インストールされている場合、自動的に後者が読み込まれる
  • implementation を設定することで明示することができる

の2点かとおもいます。この「両方がインストールされている場合」というのは、プロジェクト内の package.jsondevDependencies などに追加されている場合以外にも他のパッケージの依存に入っている場合も含まれるので、基本的には明示したほうが良いでしょう。

では、なぜ dart-sass を利用する場合に fiber プロパティにfibersパッケージを指定しているのでしょうか。

node-sassにもdart-sassにも render()renderSync() というAPIがあります。これはどちらもscssからcssへコンパイルするための関数ですが、前者が非同期に実行され、後者は同期的に実行されます。sass-loaderでは render() が利用され非同期的にscssファイルがコンパイルされることになります。

しかし、 dart-sassでは非同期にに実行する render() が非同期実行のオーバーヘッドのため renderSync() より2倍近く遅くなってしまいます。これを避けるために、利用されるのがfibresです。

fibersはもともとnode.jsに非同期に関数を実行する仕組み(Promiseやasnyc/await)がなかった頃に開発されたものです。同期関数を非同期で実行することができる Promise に相当する機能があります。(他にもGeneratorに相当する機能もありますがここでは割愛します。)

速度の早い renderSync() 関数を非同期でするために、このfibersを利用する設定が前述した webpack.config.js です。ただしfibersは作者により使用を避けるべきとされており、node 16への対応もされていません。

まとめ

つまり記事の冒頭でまとめたように、 node 16 を利用するのであればfibersを利用することはできず、コンパイル速度の低下を受け入れざるを得ません。sass-loaderではdart-sassの場合に renderSync() を利用するIssueが立っているのでもしかするとsass-loader側で対応がなされるかもしれません。

nvmとかnodenvとかanyenvをやめてasdfにした

これまでnode環境はnvmで、他はpyenvやgoenvを利用していたのですが、nvmが .node-version ファイルに対応していませんでした。(.nvmrc で指定することはできませす。)
そこでいい加減nodenvに移行したのですが、せっかくなのでどうせなら新しい物をを使おうと思いasdfに移行しました。

anyenvの問題点ははただの「**envをインストールソフト」であり、nodenvやpyenvなどそれぞれの利用法を知っている必要があります。
asdfは実行環境ごとのプラグインを介して直接バージョンを管理するため
どの実行環境でも同様に扱うことができます。
ある日突然「PHPの5.3のバイナリが欲しい…」となっても使い方を迷う必要がありません。

例としてnodejsであれば

asdf install nodejs 16.1.0

Pythonであれば

asdf install python 3.9.5

でインストールが可能です。

また、nodejsであれば lts-fermium のようにタグで指定することも可能です。
自分はグローバルにインストールするものは「一番新しいLTSの最新バージョン」を利用することにしているので、非常に便利です。(nodenvではこれができませんでした。)
また、.asdfrclegacy_version_file = yes を追加することで .node-version なども自動的に読み込み、ディレクトリによって自動的にバージョンを切り替えることもできます。

注意点としては、ビルドする関係上、使用するプラグイン(実行環境)によって依存するライブラリがあるため、そのインストールは別途必要になります。

anyenvより圧倒的に使いやすいので、anyenvやその他のバージョン管理ツールを利用している場合は、asdfへの移行を強くおすすめします。

@nuxtjs/apolloでレスポンスが空だったら404を出す

Vue ApolloがNuxtで利用可能になる @nuxtjs/apollo を使うときレスポンスが空だったら404を出したい場合があります。

下記のようにSmart Queryを使うと簡単にサーバーサイドとクライアントサイドでfetchする機能を書くことができます。

// pages/blog/_id.vue
import GetPosts from '~/apollo/GetPost.gql';

export default {
  name: 'BlogIndex',
  apollo: {
    posts: {
      query: GetPost,
      variables () {
        return {
          pageId: 123,
        };
      },
    },
  },
}

しかしSmartQueryはレスポンスのHTTPステータスコードを設定できないという問題があります。
その場合は素直に asyncData() を利用しましょう

// pages/blog/_id.vue
import GetPost from '~/apollo/GetPost.gql';

export default {
  name: 'BlogPost',
  async asyncData ({ error, app, params }) {
    const response = await app.apolloProvider.defaultClient.query({
      query: GetPost,
      variables: { pageId: params.id },
    });
    if (!response.data.post) {
      error({ statusCode: 404, message: 'Not Found' });
    }
    return { post: response.data.post };
  },
  data () {
    return {
      post: {},
    };
  },
};

ただそうなると @nuxtjs/apollo を使う意義も薄くなってしまいますね。
vue-apolloの開発もあまり活発でないので何か別の手段を使ったほうがいいんでしょうか…。

HTMLの文字コードをどうするべきか、あるいはHTMLとは何かという話

HTML文書は文字エンコーディングUTF-8でなければなりませんという記事があり、混乱があるようなのでHTMLについてHTML5とHTML Living Standard(以下HTML LSと省略)について、そしてHTMLファイルの文字コードをどうするかについて、まとめておきます。

TL;DR

  • HTMLファイルの文字コードはHTML Living Standardに従ってUTF-8にする
  • 古いSJISやEUC-JPのHTMLファイルをUTF-8に変換する必要はない

What is "HTML" ?

一般にHTMLと呼ばれる規格には複数あります。

  • HTML4.01を含むそれ以前のHTML (W3C)
  • XHTML1.1 (W3C)
  • HTML5.1 (W3C)
  • HTML Living Standard (WHATWG)

まず一旦古い話は置いておいて、HTML5とHTML LSについて考えることにします。 以下「HTML5」は特筆なき場合「W3Cの勧告したHTML5.1」の意味です。また、HTML5の日本語訳へのリンク先は5.0の文書ですのでご注意下さい。

HTML5(W3C) とHTML LS(WHATWG)の違い

HTML5とHTML LSは基本的には違いはありません。
1.2 Is this HTML5? - HTML Living Standard1.2 これはHTML5か? ― HTML Living Standard 日本語訳
にあるようにWHATWGの策定したHTML LSに基いてW3CがHTML5として勧告を出しています。

そもそもWHATWGはW3CがHTMLをアップデートしないのに業を煮やしたAppleやMozilla、Operaによって設立されました。それを後にW3Cが取り込んだのが規格としてのHTML5となります。
経緯は1.6 歴史 ― HTML Living Standardを参照するのが良いでしょう。

W3CとWHATWGのどちらに従うべきか

ブラウザの実装による、と言ってしまえばそれまでですが、基本的にHTML Living Standardを守れば良いです。ブラウザの実装もこれに則っています。またW3CではHTML LSに基いているだけで、全く同一ではありません。また、標準化のタイミングも異なります。

  1. HTML LSで標準化済みで、HTML5で勧告済み(多くの基本的な内容)
  2. HTML LSで標準化済みだが、W3Cでは未勧告(最新の内容)
  3. HTML LSで標準化済みだが、W3Cでは違いがある(一部の内容)

という3つのパターンがありえます。

3.については1.12 About this document - HTML5.1に列挙されています。

やっと文字コードの話

再掲になりますがHTML文書は文字エンコーディングUTF-8でなければなりませんの文字エンコーディングの話は

  • WHATWGでHTML Living Standardの話
  • W3Cでは未勧告(まだissueが切られただけ)

となります。
つまり、HTML4.01やXHTMLは関係がないので、古いサイトの文字コードをUTF-8に変換する必要はありません。
BOMについては、UTF-8についてはBOMをつけることが許可されているので、つけても良いですが、つけなくて良いでしょう。<meta charset="utf-8">のようにmetaタグで文字コードを指定することも禁止されていません。(が、当然ファイルそのものの文字コードと一致する必要があります。)

逆に他のSJISやUTF-16などの文字コードが利用できないかについては、HTML LSでは

the actual character encoding used to encode the document must be UTF-8.

4.2.5.5 Specifying the document's character encoding - HTML Living Standard

とあり、一方HTML5の現状では

Authors should use UTF-8. Conformance checkers may advise authors against using legacy encodings.
(中略)
Authors must not use encodings that are not defined in the WHATWG Encoding standard. Additionally, authors should not use ISO-2022-JP.

4.2.5.5 Specifying the document's character encoding - HTML5.1

と微妙に差異があります。が、これに逆らってUTF-8以外を選択するメリットは殆どないでしょう。

おわりに

Web開発者であれば、WHATWGとW3Cの文章に目を通すようにしましょう。

movファイルを手軽にgifに変換するシェル関数

パッと画面上の動きなどをSlackなどで共有したい場合に、mov形式の動画からGIFを生成するのは何かと面倒ですし、ファイルサイズも大きくなりがちです。かといってAppStoreから余計なフリーソフトなどを入れたくないですよね。そこでシェル関数という形でまとめてみました。

1. FFmpegとgifsicleをインストール

brew install ffmpeg
brew install gifsicle

などする。FFmpegは言わずと知れた動画や画像を変換するためのソフトウェアで、gifsicleはアニメーションGIF化をいい感じにやってくれるやつです。

2. 下記のシェルスクリプトを.zshrcなどにコピペする

function mov2gif() {
	mov=$1;
	if [[ -z $2 ]]; then
		width=300
	else
		width=$2
	fi
	if [[ -z $3 ]]; then
		rate=15
	else
		rate=$3
	fi
	gif=`basename $mov`".gif"
	ffmpeg -i $mov -vf scale=$width:-1 -pix_fmt rgb24 -r $rate -f gif - | gifsicle --optimize=3 --delay=3 > $gif
}

3. movファイルを変換する

mov2gif hoge.mov #オプション指定なしだと横幅300px・FPS15のGIFを出力
mov2gif hoge.mov 500 30 #と指定すれば横幅500px・FPS30で出力

アニメーションGIFのファイルサイズはFPSに大きく依存しますが、動きをよく見せたい場合もあるので、FPSを指定できるようにしてあります。
またGIF画像の高さを指定できないのは、殆どの場合キャプチャした動画は正方形に近く、サッと共有したい場合にはだいたいの画像の大きさが指定できれば十分という理由からです。

出力例

こんな感じのGIFファイルが生成できます。(横幅500pxで出力)